browser: Remove dependence on Vanadium JS
This proposal changes our code so that it will build again with npm/
browserify.
Note: The minifier still doesn't work, so NOMINIFY=1 is required.
We also depend on a local server to perform all of our namespace
client (and RPC-related) requests.
To run locally:
NOMINIFY=1 make start
make start-browserd
You can visit the namespace browser at localhost:9001, and the go
program will support it at localhost:9002.
Tracking Issue: https://github.com/vanadium/issues/issues/1268
Change-Id: Ib0dc34fb8638cbd71580f4740975ff71ba50842e
diff --git a/Makefile b/Makefile
index aec95d9..c56efcc 100644
--- a/Makefile
+++ b/Makefile
@@ -26,6 +26,7 @@
VANADIUM_JS:=$(JIRI_ROOT)/release/javascript/core
SOURCE_FILES = $(shell find src -name "*")
+GO_FILES = $(shell find src -name "*.go")
ifndef TMPDIR
export TMPDIR:=/tmp
@@ -75,7 +76,7 @@
make -C $(JIRI_ROOT)/infrastructure/deploy browser-staging
# Creating the bundle JS file.
-public/bundle.js: $(SOURCE_FILES) node_modules src/services/sample-world/ifc/index.js
+public/bundle.js: $(SOURCE_FILES) node_modules
:;jshint src # lint all src JavaScript files.
ifdef NOMINIFY
$(call BROWSERIFY,src/app.js,$@)
@@ -88,6 +89,7 @@
:;vulcanize --output public/bundle.html web-component-dependencies.html --inline
# Generate VDL for JavaScript
+# TODO(alexfandrianto): The JS Sample World is unused, so we can remove this.
src/services/sample-world/ifc/index.js:
VDLPATH=$(JIRI_ROOT)/release/projects/browser/src \
vdl generate -lang=javascript \
@@ -98,9 +100,6 @@
node_modules: package.json
:;node $(NPM) prune
:;node $(NPM) install --quiet
- # TODO(aghassemi) Temporarily use local release/javascript/core add github/npm to package.json later
- cd "$(JIRI_ROOT)/release/javascript/core" && node $(NPM) link
- :;node $(NPM) link vanadium
touch node_modules
@@ -110,9 +109,20 @@
:;bower install --config.interactive=false --quiet
touch bower_components
-go/bin: directories
+go/bin: $(GO_FILES)
+ jiri go install v.io/x/ref/cmd/principal
jiri go install v.io/x/ref/services/mounttable/mounttabled
jiri go install v.io/x/browser/runner
+ jiri go install v.io/x/browser/namespace-browserd
+
+credentials: go/bin
+ ./go/bin/principal seekblessings
+
+.PHONY: start-browserd
+# Runs namespace browser with the credentials from V23_CREDENTIALS
+start-browserd: credentials go/bin/namespace-browserd
+ ./go/bin/namespace-browserd
+
# PHONY targets:
diff --git a/bower.json b/bower.json
index e379da3..f0f9b79 100644
--- a/bower.json
+++ b/bower.json
@@ -4,5 +4,8 @@
"polymer": "Polymer/polymer#0.5.4",
"core-elements": "Polymer/core-elements#0.5.4",
"paper-elements": "Polymer/paper-elements#0.5.4"
+ },
+ "resolutions": {
+ "webcomponentsjs": "0.7.1"
}
}
diff --git a/go/src/v.io/x/browser/namespace-browserd/main.go b/go/src/v.io/x/browser/namespace-browserd/main.go
new file mode 100644
index 0000000..510f0ad
--- /dev/null
+++ b/go/src/v.io/x/browser/namespace-browserd/main.go
@@ -0,0 +1,294 @@
+// Copyright 2016 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.
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "net/url"
+ "time"
+
+ "v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/namespace"
+ "v.io/v23/naming"
+ "v.io/v23/rpc"
+ "v.io/v23/vdl"
+ "v.io/v23/vdlroot/signature"
+
+ _ "v.io/x/ref/runtime/factories/generic"
+)
+
+const (
+ RPC_TIMEOUT = 15 // 15 seconds per RPC
+
+ // TODO(alexfandrianto): Make this configurable here and on the JS end.
+ // https://github.com/vanadium/issues/issues/1268
+ SERVER_ADDRESS = "localhost:9002"
+)
+
+type NamespaceBrowser struct {
+ // The base Vanadium context. Used for all RPCs.
+ ctx *context.T
+ namespace namespace.T
+ client rpc.Client
+}
+
+// NamespaceBrowser factory
+func NewNamespaceBrowser(ctx *context.T) *NamespaceBrowser {
+ return &NamespaceBrowser{
+ ctx: ctx,
+ namespace: v23.GetNamespace(ctx),
+ client: v23.GetClient(ctx),
+ }
+}
+
+func (b *NamespaceBrowser) timed() *context.T {
+ ctx, _ := context.WithTimeout(b.ctx, RPC_TIMEOUT*time.Second)
+ return ctx
+}
+
+func writeAndFlush(rw http.ResponseWriter, data interface{}) {
+ // Make sure that the writer supports flushing.
+ flusher, ok := rw.(http.Flusher)
+ if !ok {
+ http.Error(rw, "The HTTP response writer cannot flush, so we cannot stream results!", http.StatusInternalServerError)
+ return
+ }
+
+ // Write data and flush
+ encoded, err := json.Marshal(data)
+ if err != nil {
+ log.Printf("Failed to encode data: %v, %v", data, err)
+ }
+ fmt.Fprintf(rw, "data: %s\n\n", string(encoded))
+ flusher.Flush()
+}
+
+func extractJsonString(params string) (s string, err error) {
+ err = json.Unmarshal([]byte(params), &s)
+ return
+}
+
+func extractJsonMap(params string) (m map[string]interface{}, err error) {
+ err = json.Unmarshal([]byte(params), &m)
+ return
+}
+
+/* ServeHTTP must handle EventSource requests from the browser.
+ * The requests have a "request" and "params" portion.
+ * The format is as follows:
+ *
+ * accountName: <no parameters> => { accountName: <string>, err: <err> }
+ * glob: string pattern => a stream of responses
+ * { globRes: <glob res>, globErr: <glob err>, globEnd: <bool>, err: <err> }
+ * permissions: string name => { permissions: <permissions>, err: <err> }
+ * deleteMountPoint: string name => { err: <err string> }
+ * resolveToMounttable: string name => { addresses: []<string>, err: <err> }
+ * objectAddresses: string name => { addresses: []<string>, err: <err> }
+ * remoteBlessings: string name => { blessings: []<string>, err: <err> }
+ * signature: string name => { signature: <signature>, err: <err> }
+ * makeRPC: { name: <string>, methodName: <string>, args: []<string>,
+ * numOutArgs: <int> } =>
+ * { response: <undefined, output, OR []outputs>, err: <err> }
+ */
+func (b *NamespaceBrowser) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+ // Set the headers related to event streaming.
+ rw.Header().Set("Content-Type", "text/event-stream")
+ rw.Header().Set("Cache-Control", "no-cache")
+ rw.Header().Set("Connection", "keep-alive")
+ rw.Header().Set("Access-Control-Allow-Origin", "*")
+
+ request, err := url.QueryUnescape(req.FormValue("request"))
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ params, err := url.QueryUnescape(req.FormValue("params"))
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ fmt.Println("request", request, "params", params)
+
+ // The response depends on the request type.
+ switch request {
+ case "accountName":
+ // Obtain the default blessing and return that.
+ blessing, _ := v23.GetPrincipal(b.ctx).BlessingStore().Default()
+ writeAndFlush(rw, accountNameReturn{AccountName: blessing.String()})
+ case "glob":
+ pattern, err := extractJsonString(params)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Obtain the glob stream.
+ globCh, err := b.namespace.Glob(b.timed(), pattern)
+ if err != nil {
+ writeAndFlush(rw, globReturn{Err: fmt.Sprintf("%v", err)})
+ return
+ }
+
+ // Afterwards, go through the stream and forward results and errors.
+ writeAndFlush(rw, globReturn{})
+ for entry := range globCh { // These GlobReply could be a reply or an error.
+ switch v := entry.(type) {
+ case *naming.GlobReplyEntry:
+ writeAndFlush(rw, globReturn{GlobRes: &v.Value})
+ case *naming.GlobReplyError:
+ writeAndFlush(rw, globReturn{GlobErr: &v.Value})
+ }
+ }
+ writeAndFlush(rw, globReturn{GlobEnd: true})
+ case "deleteMountPoint":
+ name, err := extractJsonString(params)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Delete the chosen name from the namespace.
+ err = b.namespace.Delete(b.timed(), name, true)
+ if err != nil {
+ writeAndFlush(rw, deleteReturn{Err: fmt.Sprintf("%v", err)})
+ return
+ }
+ writeAndFlush(rw, deleteReturn{})
+ case "resolveToMounttable":
+ name, err := extractJsonString(params)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Use the MountEntry for this name to find its server addresses.
+ entry, err := b.namespace.ResolveToMountTable(b.timed(), name)
+ if err != nil {
+ writeAndFlush(rw, addressesReturn{Err: fmt.Sprintf("%v", err)})
+ return
+ }
+ addrs := []string{}
+ for _, server := range entry.Servers {
+ addrs = append(addrs, server.Server)
+ }
+ writeAndFlush(rw, addressesReturn{Addresses: addrs})
+ case "objectAddresses":
+ name, err := extractJsonString(params)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Use the MountEntry for this name to find its object addresses.
+ entry, err := b.namespace.Resolve(b.timed(), name)
+ if err != nil {
+ writeAndFlush(rw, addressesReturn{Err: fmt.Sprintf("%v", err)})
+ return
+ }
+ addrs := []string{}
+ for _, server := range entry.Servers {
+ addrs = append(addrs, server.Server)
+ }
+ writeAndFlush(rw, addressesReturn{Addresses: addrs})
+ case "permissions":
+ name, err := extractJsonString(params)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Obtain the mount table permissions at this name.
+ perms, _, err := b.namespace.GetPermissions(b.timed(), name)
+ if err != nil {
+ writeAndFlush(rw, permissionsReturn{Err: fmt.Sprintf("%v", err)})
+ return
+ }
+ writeAndFlush(rw, permissionsReturn{Permissions: perms})
+ case "remoteBlessings":
+ name, err := extractJsonString(params)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Obtain the remote blessings for the server running at this name.
+ clientCall, err := b.client.StartCall(b.timed(), name, rpc.ReservedMethodSignature, nil)
+ defer clientCall.Finish()
+
+ if err != nil {
+ writeAndFlush(rw, blessingsReturn{Err: fmt.Sprintf("%v", err)})
+ return
+ }
+ rbs, _ := clientCall.RemoteBlessings()
+ writeAndFlush(rw, blessingsReturn{Blessings: rbs})
+ case "signature":
+ name, err := extractJsonString(params)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Obtain the signature(s) of the server running at this name.
+ var sig []signature.Interface
+ err = b.client.Call(b.timed(), name, rpc.ReservedSignature, nil, []interface{}{&sig})
+ if err != nil {
+ writeAndFlush(rw, signatureReturn{Err: fmt.Sprintf("%v", err)})
+ return
+ }
+ convertedSig := convertSignature(sig)
+ writeAndFlush(rw, signatureReturn{Signature: convertedSig})
+ case "makeRPC":
+ data, err := extractJsonMap(params)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ name := data["name"].(string)
+ method := data["methodName"].(string)
+ params := data["args"].([]interface{})
+ fmt.Printf("Make RPC: %s %s %v\n", name, method, params)
+ numOutArgs := int(data["numOutArgs"].(float64))
+
+ // Prepare outargs as *vdl.Value
+ outargs := make([]*vdl.Value, numOutArgs)
+ outptrs := make([]interface{}, numOutArgs)
+ for i := range outargs {
+ outptrs[i] = &outargs[i]
+ }
+
+ // Make the call to name's method with the given params.
+ err = b.client.Call(b.timed(), name, method, params, outptrs)
+ if err != nil {
+ writeAndFlush(rw, makeRPCReturn{Err: fmt.Sprintf("%v", err)})
+ return
+ }
+
+ // Convert the *vdl.Value outputs to readable strings
+ resStrings := []string{}
+ for _, outarg := range outargs {
+ resStrings = append(resStrings, outarg.String())
+ }
+ writeAndFlush(rw, makeRPCReturn{Response: resStrings})
+ default:
+ writeAndFlush(rw, "Please connect from the namespace browser.")
+ }
+
+ // I think this keeps the connection open until the other side closes it?
+ notify := rw.(http.CloseNotifier).CloseNotify()
+ <-notify
+}
+
+func main() {
+ ctx, shutdown := v23.Init()
+ defer shutdown()
+
+ browser := NewNamespaceBrowser(ctx)
+ log.Fatal("HTTP server error: ", http.ListenAndServe(SERVER_ADDRESS, browser))
+}
diff --git a/go/src/v.io/x/browser/namespace-browserd/types.go b/go/src/v.io/x/browser/namespace-browserd/types.go
new file mode 100644
index 0000000..8059e8f
--- /dev/null
+++ b/go/src/v.io/x/browser/namespace-browserd/types.go
@@ -0,0 +1,171 @@
+// Copyright 2016 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.
+
+package main
+
+/*
+ * Defines types to organize the HTTP response values.
+ * Since json.Marshal only works on exported fields, the types here force the
+ * fields to be converted to lowercase (for better JS consumption).
+ */
+
+import (
+ "v.io/v23/naming"
+ "v.io/v23/security/access"
+ "v.io/v23/vdl"
+ "v.io/v23/vdlroot/signature"
+)
+
+type accountNameReturn struct {
+ AccountName string `json:"accountName"`
+ Err string `json:"err"`
+}
+
+type globReturn struct {
+ GlobRes *naming.MountEntry `json:"globRes"`
+ GlobErr *naming.GlobError `json:"globErr"`
+ GlobEnd bool `json:"globEnd"`
+ Err string `json:"err"`
+}
+
+type deleteReturn struct {
+ Err string `json:"err"`
+}
+
+type addressesReturn struct {
+ Addresses []string `json:"addresses"`
+ Err string `json:"err"`
+}
+
+type permissionsReturn struct {
+ Permissions access.Permissions `json:"permissions"`
+ Err string `json:"err"`
+}
+
+type blessingsReturn struct {
+ Blessings []string `json:"blessings"`
+ Err string `json:"err"`
+}
+
+type signatureReturn struct {
+ Signature []pInterface `json:"signature"`
+ Err string `json:"err"`
+}
+
+type makeRPCReturn struct {
+ Response []string `json:"response"`
+ Err string `json:"err"`
+}
+
+// pInterface describes the signature of an interface.
+// This is a parallel data structure to signature.Interface.
+// The reason to do this is so that Type and Tags can be converted to strings
+// before being sent along the wire.
+type pInterface struct {
+ Name string `json:"name"`
+ PkgPath string `json:"pkgPath"`
+ Doc string `json:"doc"`
+ Embeds []pEmbed `json:"embeds"` // No special ordering.
+ Methods []pMethod `json:"methods"` // Ordered by method name.
+}
+
+// pEmbed describes the signature of an embedded interface.
+type pEmbed struct {
+ Name string `json:"name"`
+ PkgPath string `json:"pkgPath"`
+ Doc string `json:"doc"`
+}
+
+// pMethod describes the signature of an interface method.
+type pMethod struct {
+ Name string `json:"name"`
+ Doc string `json:"doc"`
+ InArgs []pArg `json:"inArgs"` // Input arguments
+ OutArgs []pArg `json:"outArgs"` // Output arguments
+ InStream *pArg `json:"inStream"` // Input stream (optional)
+ OutStream *pArg `json:"outStream"` // Output stream (optional)
+ Tags []string `json:"tags"` // Method tags
+}
+
+// pArg describes the signature of a single argument.
+type pArg struct {
+ Name string `json:"name"`
+ Doc string `json:"doc"`
+ Type string `json:"type"` // Type of the argument.
+}
+
+// Helper method to convert []signature.Interface to the parallel data
+// structure, []pInterface.
+func convertSignature(sig []signature.Interface) []pInterface {
+ ret := []pInterface{}
+ for _, ifc := range sig {
+ pIfc := pInterface{
+ Name: ifc.Name,
+ PkgPath: ifc.PkgPath,
+ Doc: ifc.Doc,
+ Embeds: convertEmbeds(ifc.Embeds),
+ Methods: convertMethods(ifc.Methods),
+ }
+ ret = append(ret, pIfc)
+ }
+ return ret
+}
+
+func convertEmbeds(embeds []signature.Embed) []pEmbed {
+ ret := []pEmbed{}
+ for _, e := range embeds {
+ pE := pEmbed{
+ Name: e.Name,
+ PkgPath: e.PkgPath,
+ Doc: e.Doc,
+ }
+ ret = append(ret, pE)
+ }
+ return ret
+}
+
+func convertMethods(methods []signature.Method) []pMethod {
+ ret := []pMethod{}
+ for _, m := range methods {
+ pM := pMethod{
+ Name: m.Name,
+ Doc: m.Doc,
+ InArgs: convertArgs(m.InArgs),
+ OutArgs: convertArgs(m.OutArgs),
+ InStream: convertOptArg(m.InStream),
+ OutStream: convertOptArg(m.OutStream),
+ Tags: convertTags(m.Tags),
+ }
+ ret = append(ret, pM)
+ }
+ return ret
+}
+
+func convertArgs(args []signature.Arg) []pArg {
+ ret := []pArg{}
+ for _, a := range args {
+ pA := *convertOptArg(&a)
+ ret = append(ret, pA)
+ }
+ return ret
+}
+
+func convertOptArg(arg *signature.Arg) *pArg {
+ if arg == nil {
+ return nil
+ }
+ return &pArg{
+ Name: arg.Name,
+ Doc: arg.Doc,
+ Type: arg.Type.String(),
+ }
+}
+
+func convertTags(tags []*vdl.Value) []string {
+ ret := []string{}
+ for _, t := range tags {
+ ret = append(ret, t.String())
+ }
+ return ret
+}
diff --git a/package.json b/package.json
index 40b83f0..e80e96b 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,6 @@
"browserify": "^4.2.3",
"exorcist": "^0.1.6",
"jshint": "^2.6.0",
- "prova": "git+https://github.com/aghassemi/prova#0.0.4",
"proxyquireify": "^0.5.0",
"rework": "^1.0.1",
"rework-import": "^1.2.1",
diff --git a/src/app.js b/src/app.js
index 3bd8ef3..1ebb55b 100644
--- a/src/app.js
+++ b/src/app.js
@@ -4,7 +4,6 @@
var uuid = require('uuid');
var mercury = require('mercury');
-var vanadium = require('vanadium');
var addDelegatedEvents = require('./lib/mercury/add-delegated-events');
var onboarding = require('./onboarding');
var router = require('./router');
@@ -17,7 +16,6 @@
var views = require('./components/browse/views');
var userAccount = require('./components/user-account');
var namespaceService = require('./services/namespace/service');
-var sampleWorld = require('./services/sample-world');
var stateService = require('./services/state/service');
var errorRoute = require('./routes/error');
var browseRoute = require('./routes/browse');
@@ -264,16 +262,9 @@
*/
function initVanadium() {
viewport.setSplashMessage('Initializing Vanadium...');
- namespaceService.initVanadium().then(function(vruntime) {
- vruntime.once('crash', onVanadiumCrash);
-
+ namespaceService.getEmailAddress().then(function(email) {
// Onboarding Hook for new users (async). Currently does not block.
- onboarding(vruntime, state);
-
- window.addEventListener('beforeunload', function() {
- log.debug('closing runtime');
- vruntime.close();
- });
+ onboarding(email, state);
if (state.demo()) {
return intializeDemo();
@@ -293,7 +284,10 @@
* Initialized the demo mode
*/
function intializeDemo() {
- viewport.setSplashMessage('Initializing Sample World Demo...');
+ // TODO(alexfandrianto): With JS deprecated, we may want to delete the old
+ // demo code. Instead, demo services should be launched by cmdline or Go.
+ initialized();
+ /*viewport.setSplashMessage('Initializing Sample World Demo...');
var sampleWorldDirectory = '';
return sampleWorld.getRootedName().then(function(name) {
sampleWorldDirectory = name;
@@ -307,22 +301,11 @@
viewType: 'tree'
})
});
- });
+ });*/
}
}).catch(function(err) {
- if (err instanceof vanadium.verror.ExtensionNotInstalledError) {
- vanadium.extension.promptUserToInstallExtension();
- } else {
- var isError = true;
- viewport.setSplashMessage(err.toString(), isError);
- log.error(err);
- }
+ var isError = true;
+ viewport.setSplashMessage(err.toString(), isError);
+ log.error(err);
});
}
-
-/*
- * Handler if Vanadium runtime crashes
- */
-function onVanadiumCrash(crashErr) {
- events.browse.error(crashErr);
-}
\ No newline at end of file
diff --git a/src/components/browse/item-details/method-form/index.js b/src/components/browse/item-details/method-form/index.js
index 8ab341b..80c5b7d 100644
--- a/src/components/browse/item-details/method-form/index.js
+++ b/src/components/browse/item-details/method-form/index.js
@@ -146,9 +146,12 @@
*/
function initializeInputArguments(state) {
var param = getMethodData(state.interface(), state.methodName());
- var startingArgs = _.range(param.inArgs.length).map(function() {
- return ''; // Initialize array with empty string values using lodash.
- });
+ var startingArgs = [];
+ if (param.inArgs) {
+ startingArgs = _.range(param.inArgs.length).map(function() {
+ return ''; // Initialize array with empty string values using lodash.
+ });
+ }
setMercuryArray(state.args, startingArgs);
}
@@ -161,9 +164,11 @@
interface: state.interface(),
methodName: state.methodName(),
};
+ var inArgList = param.inArgs || [];
+
return Promise.all(
// For each argname, predict which inputs should be suggested.
- param.inArgs.map(function(inArg, i) {
+ inArgList.map(function(inArg, i) {
return smartService.predict(
'learner-method-input',
_.assign({argName: inArg.name}, input)
@@ -388,7 +393,7 @@
var tooltip = methodSig.doc || '<no description>';
// If the method takes input, add documentation about the input arguments.
- if (methodSig.inArgs.length > 0) {
+ if (methodSig.inArgs && methodSig.inArgs.length > 0) {
tooltip += '\n\nParameters';
methodSig.inArgs.forEach(function(inArg) {
tooltip += '\n';
@@ -400,7 +405,7 @@
}
// If the method returns output, add documentation about the output arguments.
- if (methodSig.outArgs.length > 0) {
+ if (methodSig.outArgs && methodSig.outArgs.length > 0) {
tooltip += '\n\nOutput';
methodSig.outArgs.forEach(function(outArg) {
tooltip += '\n';
@@ -412,11 +417,11 @@
}
// If the method has tags, show the tag types and values.
- if (methodSig.tags.length > 0) {
+ if (methodSig.tags && methodSig.tags.length > 0) {
tooltip += '\n\nTags';
methodSig.tags.forEach(function(tag) {
tooltip += '\n';
- tooltip += '- ' + tag.val + ': ' + tag._type.name;
+ tooltip += '- ' + tag;
});
}
@@ -459,12 +464,13 @@
var methodName = state.methodName;
var param = getMethodData(state.interface, methodName);
var text = methodName;
- var hasArgs = (param.inArgs.length > 0);
+ var inArgList = param.inArgs || [];
+ var hasArgs = (inArgList.length > 0);
if (hasArgs) {
text += '(';
}
if (args !== undefined) {
- for (var i = 0; i < param.inArgs.length; i++) {
+ for (var i = 0; i < inArgList.length; i++) {
if (i > 0) {
text += ', ';
}
@@ -674,10 +680,13 @@
* from state.args, a starred invocation's arguments, or a recommendation's.
*/
function getRunEvent(state, events, args) {
+ var methodData = getMethodData(state.interface, state.methodName);
+ var outArgList = methodData.outArgs || [];
return mercury.event(events.runAction, {
name: state.itemName,
methodName: state.methodName,
- args: args
+ args: args,
+ numOutArgs: outArgList.length
});
}
diff --git a/src/components/browse/item-details/method-form/make-rpc.js b/src/components/browse/item-details/method-form/make-rpc.js
index c1b3093..04eed02 100644
--- a/src/components/browse/item-details/method-form/make-rpc.js
+++ b/src/components/browse/item-details/method-form/make-rpc.js
@@ -30,8 +30,8 @@
}
});
- return namespaceService.makeRPC(data.name, data.methodName, args).catch(
- function(err) {
+ return namespaceService.makeRPC(
+ data.name, data.methodName, args, data.numOutArgs).catch(function(err) {
log.error('Error during RPC',
data.name,
data.methodName,
diff --git a/src/components/browse/item-details/mount-point-details/display-mountpoint-details.js b/src/components/browse/item-details/mount-point-details/display-mountpoint-details.js
index 2bd3733..7b83c93 100644
--- a/src/components/browse/item-details/mount-point-details/display-mountpoint-details.js
+++ b/src/components/browse/item-details/mount-point-details/display-mountpoint-details.js
@@ -2,7 +2,6 @@
// 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 namespaceService = require('../../../../services/namespace/service');
var log = require('../../../../lib/log')(
@@ -74,7 +73,9 @@
state.put('permissions', permissions);
}
}).catch(function(err) {
- if (err instanceof vanadium.verror.NoAccessError) {
+ // TODO(alexfandrianto): We don't have access to VErrors, so this is the
+ // closest we can get to determining "notAuthorizedToSeePermissions".
+ if (err.toString().contains('NoAccess')) {
state.put('notAuthorizedToSeePermissions', true);
}
log.error('Failed to get mountpoint permissions for:', name, err);
diff --git a/src/onboarding.js b/src/onboarding.js
index 66be5a8..6273fd2 100644
--- a/src/onboarding.js
+++ b/src/onboarding.js
@@ -10,11 +10,11 @@
module.exports = onboarding;
// When a new user visits the namespace browser, do a simple onboarding.
-function onboarding(rt, appState) {
+function onboarding(email, appState) {
store.getValue('returning_user').then(function(returning) {
if (!returning) {
log.debug('Welcome to the Vanadium Namespace Browser!');
- addDefaultBookmarks(rt);
+ addDefaultBookmarks(email);
// TODO(alexfandrianto): We can improve the onboarding experience
// By changing the appState variable, we can do other things to help a new
@@ -27,13 +27,11 @@
}
// Add default bookmarks to the user's store.
-function addDefaultBookmarks(rt) {
+function addDefaultBookmarks(email) {
function bookmarkFail(err) {
log.warn('Could not add default bookmark', err);
}
- var email = getEmailFromAccountName(rt.accountName);
-
// Determine the default bookmarks.
var globalMT = '/ns.dev.v.io:8101';
var personal;
@@ -61,11 +59,6 @@
}).catch(bookmarkFail);
}
-function getEmailFromAccountName(accountName) {
- // Use a regular expression to extract the email.
- return /dev.v.io\/u\/(.*?)\//.exec(accountName)[1];
-}
-
// Set the returning_user flag to true.
function completeOnboarding() {
store.setValue('returning_user', true).catch(function(err) {
diff --git a/src/services/namespace/interface-util.js b/src/services/namespace/interface-util.js
index be6b62a..0f30ec5 100644
--- a/src/services/namespace/interface-util.js
+++ b/src/services/namespace/interface-util.js
@@ -33,9 +33,10 @@
function hashInterface(interface) {
var pkgPath = interface.pkgPath + '.' + interface.name;
var methods = interface.methods.map(function(methodData) {
+ var inArgList = methodData.inArgs || [];
return {
name: methodData.name,
- inArgs: methodData.inArgs.map(function(inArg) {
+ inArgs: inArgList.map(function(inArg) {
return inArg.name;
})
};
diff --git a/src/services/namespace/naming-util.js b/src/services/namespace/naming-util.js
new file mode 100644
index 0000000..5379fb4
--- /dev/null
+++ b/src/services/namespace/naming-util.js
@@ -0,0 +1,175 @@
+// Copyright 2016 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.
+
+/*
+ * @fileoverview Helpers for manipulating vanadium names.
+ * See vanadium/release/go/src/v.io/v23/naming/parse.go for the
+ * corresponding operations in golang.
+ * @private
+ */
+
+module.exports = {
+ clean: clean,
+ join: join,
+ isRooted: isRooted,
+ basename: basename,
+ stripBasename: stripBasename,
+ splitAddressName: splitAddressName,
+};
+
+/**
+ * Normalizes a name by collapsing multiple slashes and removing any
+ * trailing slashes.
+ * @param {string} name The vanadium name.
+ * @returns {string} The clean name.
+ * @memberof module:vanadium.naming
+ */
+function clean(name) {
+ return _removeTailSlash(_squashMultipleSlashes(name));
+}
+
+/**
+ * <p>Joins parts of a name into a whole. The joined name will be cleaned; it
+ * only preserved the rootedness of the name components.</p>
+ * <p>Examples:</p>
+ * <pre>
+ * join(['a, b']) -> 'a/b'
+ * join('/a/b/', '//d') -> '/a/b/d'
+ * join('//a/b', 'c/') -> '/a/b/c'
+ * </pre>
+ * @param {...string} parts Either a single array that contains the strings
+ * to join or a variable number of string arguments that will be joined.
+ * @return {string} A joined string.
+ * @memberof module:vanadium.naming
+ */
+function join(parts) {
+ if (Array.isArray(parts)) {
+ while (parts.length > 0 && parts[0] === '') {
+ parts.splice(0, 1); // Remove empty strings; they add nothing to the join.
+ }
+ var joined = parts.join('/');
+ return clean(joined);
+ }
+ return join(Array.prototype.slice.call(arguments));
+}
+
+/**
+ * Determines if a name is rooted, that is beginning with a single '/'.
+ * @param {string} name The vanadium name.
+ * @return {boolean} True iff the name is rooted.
+ * @memberof module:vanadium.naming
+ */
+function isRooted(name) {
+ return name[0] === '/';
+}
+
+/**
+ * SplitAddressName takes an object name and returns the server address and
+ * the name relative to the server.
+ * The name parameter may be a rooted name or a relative name; an empty string
+ * address is returned for the latter case.
+ * @param {string} name The vanadium name.
+ * @return {Object.<string, string>} An object with the address and suffix
+ * split. Returned object will be in the format of:
+ * <pre>
+ * {address: string, suffix: string}
+ * </pre>
+ * Address may be in endpoint format or host:port format.
+ * @memberof module:vanadium.naming
+ */
+function splitAddressName(name) {
+ name = clean(name);
+
+ if (!isRooted(name)) {
+ return {
+ address: '',
+ suffix: name
+ };
+ }
+ name = name.substr(1); // trim the beginning "/"
+ if (name.length === 0) {
+ return {
+ address: '',
+ suffix: ''
+ };
+ }
+
+ if (name[0] === '@') { // <endpoint>/<suffix>
+ var split = _splitIntoTwo(name, '@@/');
+ if (split.suffix.length > 0) { // The trailing "@@" was stripped, restore
+ split.address = split.address + '@@';
+ }
+ return split;
+ }
+ if (name[0] === '(') { // (blessing)@host:[port]/suffix
+ var tmp = _splitIntoTwo(name, ')@').suffix;
+ var suffix = _splitIntoTwo(tmp, '/').suffix;
+ return {
+ address: _trimEnd(name, '/' + suffix),
+ suffix: suffix
+ };
+ }
+ // host:[port]/suffix
+ return _splitIntoTwo(name, '/');
+
+ function _splitIntoTwo(str, separator) {
+ var elems = str.split(separator);
+ return {
+ address: elems[0],
+ suffix: elems.splice(1).join(separator)
+ };
+ }
+}
+
+/**
+ * Gets the basename of the given vanadium name.
+ * @param {string} name The vanadium name.
+ * @return {string} The basename of the given name.
+ * @memberof module:vanadium.naming
+ */
+function basename(name) {
+ name = clean(name);
+ var split = splitAddressName(name);
+ if (split.suffix !== '') {
+ return split.suffix.substring(split.suffix.lastIndexOf('/') + 1);
+ } else {
+ return split.address;
+ }
+}
+
+/**
+ * Retrieves the parent of the given name.
+ * @param {string} name The vanadium name.
+ * @return {string | null} The parent's name or null, if there isn't one.
+ * @memberof module:vanadium.naming
+ */
+function stripBasename(name) {
+ name = clean(name);
+ var split = splitAddressName(name);
+ if (split.suffix !== '') {
+ return name.substring(0, name.lastIndexOf('/'));
+ } else {
+ return '';
+ }
+}
+
+// Replace every group of slashes in the string with a single slash.
+function _squashMultipleSlashes(s) {
+ return s.replace(/\/{2,}/g, '/');
+}
+
+// Remove the last slash in the string, if any.
+function _removeTailSlash(s) {
+ return s.replace(/\/$/g, '');
+}
+
+// Helper util that removes the given suf from the end of str
+function _trimEnd(str, suf) {
+ var index = str.lastIndexOf(suf);
+ if (index + suf.length === str.length) {
+ return str.substring(0, index);
+ } else {
+ return str;
+ }
+}
diff --git a/src/services/namespace/service.js b/src/services/namespace/service.js
index 97451fe..7c418fe 100644
--- a/src/services/namespace/service.js
+++ b/src/services/namespace/service.js
@@ -2,16 +2,14 @@
// 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 mercury = require('mercury');
var LRU = require('lru-cache');
var EventEmitter = require('events').EventEmitter;
-var vanadiumConfig = require('../../vanadium-config');
var itemFactory = require('./item');
var freeze = require('../../lib/mercury/freeze');
var sortedPush = require('../../lib/mercury/sorted-push-array');
var log = require('../../lib/log')('services:namespace:service');
-var naming = vanadium.naming;
+var naming = require('./naming-util.js');
naming.parseName = parseName;
module.exports = {
@@ -20,42 +18,97 @@
getRemoteBlessings: getRemoteBlessings,
getSignature: getSignature,
getAccountName: getAccountName,
+ getEmailAddress: getEmailAddress,
getObjectAddresses: getObjectAddresses,
getPermissions: getPermissions,
resolveToMounttable: resolveToMounttable,
makeRPC: makeRPC,
search: search,
util: naming,
- initVanadium: getRuntime,
clearCache: clearCache,
deleteMountPoint: deleteMountPoint,
prefixes: prefixes
};
-//TODO(aghassemi) What's a good timeout? It should be shorter than this.
-//Can we use ML to dynamically change the timeout?
-//Should this be a user settings?
-var RPC_TIMEOUT = 15 * 1000;
/*
- * Lazy getter and initializer for Vanadium runtime
+ * Returns an EventSource object connected to the local network.
+ * Only certain types of requests are allowed.
+ *
+ * accountName: <no parameters> => { accountName: <string>, err: <err> }
+ * glob: string pattern => a stream of responses
+ * { globRes: <glob res>, globErr: <glob err>, globEnd: <bool>, err: <err> }
+ * permissions: string name => { permissions: <permissions>, err: <err> }
+ * deleteMountPoint: string name => { err: <err string> }
+ * resolveToMounttable: string name => { addresses: []<string>, err: <err> }
+ * objectAddresses: string name => { addresses: []<string>, err: <err> }
+ * remoteBlessings: string name => { blessings: []<string>, err: <err> }
+ * signature: string name => { signature: <signature>, err: <err> }
+ * makeRPC: { name: <string>, methodName: <string>, args: []<string>,
+ * numOutArgs: <int> } =>
+ * { response: <undefined, output, OR []outputs>, err: <err> }
*/
-var _runtimePromiseInstance;
+// TODO(alexfandrianto): Make this configurable here and on the Go end.
+// https://github.com/vanadium/issues/issues/1268
+var eventSourceURL = 'http://127.0.0.1:9002';
+function connectToEventSource(requestType, parameters) {
+ parameters = parameters || '';
+ var requestData = eventSourceURL +
+ '?request=' + encodeURIComponent(requestType) +
+ '¶ms=' + encodeURIComponent(JSON.stringify(parameters));
-function getRuntime() {
- if (!_runtimePromiseInstance) {
- _runtimePromiseInstance = vanadium.init(vanadiumConfig);
- }
- return _runtimePromiseInstance;
+ // Create the EventSource. Note: node does not have EventSource.
+ return new EventSource(requestData); // jshint ignore:line
+}
+
+/*
+ * Returns a Promise<value> drawn from the connected Event Source.
+ * See connectToEventSource.
+ */
+function getSingleEvent(type, params, field) {
+ return new Promise(function(resolve, reject) {
+ var ev = connectToEventSource(type, params);
+ ev.addEventListener('message', function(message) {
+ ev.close();
+ try {
+ var data = JSON.parse(message.data);
+ if (data.err) {
+ reject(data.err);
+ } else {
+ resolve(data[field]);
+ }
+ } catch (err) {
+ reject(err);
+ }
+ });
+ ev.addEventListener('error', function(err) {
+ ev.close();
+ reject(err);
+ });
+ });
}
/*
* Returns the accountName for the currently logged in user
* @return {Promise.<string>}
*/
+var _accountNamePromise;
function getAccountName() {
- return getRuntime().then(function(rt) {
- return rt.accountName;
+ if (!_accountNamePromise) {
+ _accountNamePromise =
+ getSingleEvent('accountName', undefined, 'accountName');
+ }
+ return _accountNamePromise;
+}
+
+/*
+ * Returns the email address for the currently logged in user
+ * @return {Promise.<string>}
+ */
+function getEmailAddress() {
+ return getAccountName().then(function(accountName) {
+ // TODO(alexfandrianto): Assumes a lot about the format of the blessings.
+ return accountName.split(':')[2];
});
}
@@ -104,43 +157,92 @@
var globItemsObservArr = mercury.array([]);
var immutableResult = freeze(globItemsObservArr);
immutableResult.events = new EventEmitter();
- var ctx;
var globItemsObservArrPromise =
- getRuntime().then(function callGlobOnNamespace(rt) {
- ctx = rt.getContext().withTimeout(RPC_TIMEOUT);
- // TODO(aghassemi) use watchGlob when available
- var namespace = rt.getNamespace();
- return namespace.glob(ctx, pattern).stream;
- }).then(function updateResult(globStream) {
- globStream.on('data', function createItem(globResult) {
- // Create an item as glob results come in and add the item to result
- var item = createNamespaceItem(globResult);
+ Promise.resolve().then(function callGlobOnNamespace() {
+ return new Promise(function (resolve, reject) {
+ var ev = connectToEventSource('glob', pattern);
+ ev.addEventListener('error', function(err) {
+ ev.close();
+ reject(err);
+ });
- var existingItem = globItemsObservArr.filter(function(curItem) {
- return curItem().objectName === item().objectName;
- }).get(0);
- if (existingItem) {
- // override the old one if new item has server
- if (item().hasServer) {
- var index = globItemsObservArr.indexOf(existingItem);
- globItemsObservArr.put(index, item);
+ function handleMessageConnectionResponse(response) {
+ try {
+ var data = JSON.parse(response.data);
+ if (data.err) {
+ ev.close();
+ reject(data.err);
+ } else {
+ // We have successfully established the stream.
+ // Keep the event source open to listen for more.
+ resolve();
+ }
+ } catch (err) {
+ ev.close();
+ reject(err);
}
- } else {
- var sorter = 'mountedName';
- sortedPush(globItemsObservArr, item, sorter);
}
- });
- globStream.on('end', function() {
- immutableResult.events.emit('end');
- immutableResult._hasEnded = true;
- });
+ function handleStreamEvent(message) {
+ try {
+ var data = JSON.parse(message.data);
- globStream.on('error', function emitGlobErrorAndLog(err) {
- immutableResult.events.emit('globError', err);
- log.warn('Glob stream error for', name, err);
- });
+ if (data.globRes) {
+ var globResult = data.globRes;
+ // Handle a glob result by creating an item.
+ var item = createNamespaceItem(lowercasifyJSONObject(globResult));
+
+ var existingItem = globItemsObservArr.filter(function(curItem) {
+ return curItem().objectName === item().objectName;
+ }).get(0);
+ if (existingItem) {
+ // override the old one if new item has server
+ if (item().hasServer) {
+ var index = globItemsObservArr.indexOf(existingItem);
+ globItemsObservArr.put(index, item);
+ }
+ } else {
+ var sorter = 'mountedName';
+ sortedPush(globItemsObservArr, item, sorter);
+ }
+
+ } else if (data.globErr) {
+ var err = data.globErr;
+ // Handle a glob error by emitting that event.
+ immutableResult.events.emit('globError', err);
+ log.warn('Glob stream error', err);
+ } else if (data.globEnd) {
+ // Handle a glob end by emitting it. Close event source.
+ immutableResult.events.emit('end');
+ immutableResult._hasEnded = true;
+ ev.close();
+ } else {
+ // There was a data error. Close event source.
+ log.error('Event source error for', name, data.err);
+ ev.close();
+
+ // If this were an RPC, we probably would have failed earlier.
+ // So, we must also clear the cache key.
+ globCache.del(cacheKey);
+ }
+ } catch (err) {
+ log.error(err);
+ }
+ }
+
+ var initialResponse = false;
+ ev.addEventListener('message', function(response) {
+ if (!initialResponse) {
+ // Check whether the RPC and stream connection was established.
+ initialResponse = true;
+ handleMessageConnectionResponse(response);
+ } else {
+ // Handle the Glob Results and Errors from the stream.
+ handleStreamEvent(response);
+ }
+ });
+ });
}).then(function cacheAndReturnResult() {
globCache.set(cacheKey, immutableResult);
return immutableResult;
@@ -215,28 +317,29 @@
* vanadium.security.Permissions object.
*/
function getPermissions(name) {
- return getRuntime().then(function(rt) {
- var ctx = rt.getContext().withTimeout(RPC_TIMEOUT);
- var ns = rt.getNamespace();
- return ns.getPermissions(ctx, name);
- }).then(function(results) {
- // getPermissions return multiple results, permissions is at
- // outArg position 0
- return mercury.value(results[0]);
+ return getSingleEvent('permissions', name, 'permissions').then(
+ function(perms) {
+ // perms is an object, but we want a map instead.
+ var p2 = new Map();
+ for (var key in perms) {
+ if (perms.hasOwnProperty(key)) {
+ p2.set(key, lowercasifyJSONObject(perms[key]));
+ }
+ }
+ return mercury.value(p2);
});
}
+
+
/*
* Deletes a mount point.
* @param {string} name mountpoint name to delete.
* @return {Promise<void>} Success or failure promise.
*/
function deleteMountPoint(name) {
- return getRuntime().then(function(rt) {
- var ctx = rt.getContext().withTimeout(RPC_TIMEOUT);
- var ns = rt.getNamespace();
- return ns.delete(ctx, name, true);
- });
+ // Note: The return value on success is undefined.
+ return getSingleEvent('deleteMountPoint', name, 'deleteMountPoint');
}
/*
@@ -246,12 +349,9 @@
* objectAddress strings.
*/
function resolveToMounttable(name) {
- return getRuntime().then(function(rt) {
- var ctx = rt.getContext().withTimeout(RPC_TIMEOUT);
- var ns = rt.getNamespace();
- return ns.resolveToMounttable(ctx, name);
- }).then(function(objectAddresses) {
- return mercury.array(objectAddresses);
+ return getSingleEvent('resolveToMounttable', name, 'addresses').then(
+ function(objectAddresses) {
+ return mercury.array(objectAddresses);
});
}
@@ -262,12 +362,9 @@
* array of string objectAddresses
*/
function getObjectAddresses(name) {
- return getRuntime().then(function resolve(rt) {
- var resolveCtx = rt.getContext().withTimeout(RPC_TIMEOUT);
- var ns = rt.getNamespace();
- return ns.resolve(resolveCtx, name);
- }).then(function(objectAddresses) {
- return mercury.array(objectAddresses);
+ return getSingleEvent('objectAddresses', name, 'addresses').then(
+ function(objectAddresses) {
+ return mercury.array(objectAddresses);
});
}
@@ -292,14 +389,11 @@
if (cacheHit) {
return Promise.resolve(cacheHit);
}
- return getRuntime().then(function invokeRemoteBlessingsMethod(rt) {
- var ctx = rt.getContext().withTimeout(RPC_TIMEOUT);
- var client = rt.getClient();
- return client.remoteBlessings(ctx, objectName);
- }).then(function cacheAndReturnRemoteBlessings(remoteBlessings) {
- // Remote Blessings is []string representing the principals of the service.
- remoteBlessingsCache.set(cacheKey, remoteBlessings);
- return remoteBlessings;
+ return getSingleEvent('remoteBlessings', objectName, 'blessings').then(
+ function cacheAndReturnRemoteBlessings(remoteBlessings) {
+ // Remote Blessings is []string of the service's blessings.
+ remoteBlessingsCache.set(cacheKey, remoteBlessings);
+ return remoteBlessings;
});
}
@@ -324,40 +418,66 @@
if (cacheHit) {
return Promise.resolve(cacheHit);
}
- return getRuntime().then(function invokeSignatureMethod(rt) {
- var ctx = rt.getContext().withTimeout(RPC_TIMEOUT);
- var client = rt.getClient();
- return client.signature(ctx, objectName);
- }).then(function cacheAndReturnSignature(signature) {
- // Signature is []interface; each interface contains method data.
- signatureCache.set(cacheKey, signature);
- return signature;
+ return getSingleEvent('signature', objectName, 'signature').then(
+ function cacheAndReturnSignature(signature) {
+ // Signature is []interface; each interface contains method data.
+ signatureCache.set(cacheKey, signature);
+ return signature;
});
}
+// Go through the JSON object and lowercase everything recursively.
+// We need this because it is annoying to change every Go struct to have the
+// json annotation to lowercase its field name.
+function lowercasifyJSONObject(obj) {
+ // number, string, boolean, or null
+ if (typeof obj !== 'object' || obj === null) {
+ return obj; // It's actually primitive.
+ }
+
+ // array
+ if (Array.isArray(obj)) {
+ var a = [];
+ for (var i = 0; i < obj.length; i++) {
+ a[i] = lowercasifyJSONObject(obj[i]);
+ }
+ return a;
+ }
+
+ // object
+ var cp = {};
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k)) {
+ var lowerK = k[0].toLowerCase() + k.substr(1);
+ cp[lowerK] = lowercasifyJSONObject(obj[k]);
+ }
+ }
+ return cp;
+}
+
/*
* Make an RPC call on a service object.
* name: string representing the name of the service
* methodName: string for the service method name
* args (optional): array of arguments for the service method
*/
-function makeRPC(name, methodName, args) {
- // Adapt the method name to be lowercase again.
- methodName = methodName[0].toLowerCase() + methodName.substr(1);
-
- var ctx;
- return getRuntime().then(function bindToName(rt) {
- ctx = rt.getContext();
- var client = rt.getClient();
- return client.bindTo(ctx, name);
- }).then(function callMethod(service) {
- log.debug('Calling', methodName, 'on', name, 'with', args);
- args.unshift(ctx.withTimeout(RPC_TIMEOUT));
- return service[methodName].apply(null, args);
- }).then(function returnResult(result) {
+function makeRPC(name, methodName, args, numOutArgs) {
+ log.debug('Calling', methodName, 'on', name, 'with', args, 'for', numOutArgs);
+ var call = {
+ name: name,
+ methodName: methodName,
+ args: args,
+ numOutArgs: numOutArgs
+ };
+ return getSingleEvent('makeRPC', call, 'response').then(function (result) {
// If the result was for 0 outArg, then this returns undefined.
// If the result was for 1 outArg, then it gets a single output.
// If the result was for >1 outArgs, then we return []output.
+ if (numOutArgs === 0) {
+ return;
+ } else if (numOutArgs === 1) {
+ return result[0];
+ }
return result;
});
}
@@ -371,18 +491,16 @@
* about an item.
*/
function createNamespaceItem(mountEntry) {
-
var name = mountEntry.name;
// mounted name relative to parent
var mountedName = naming.basename(name);
var isLeaf = mountEntry.isLeaf;
- var hasServer = mountEntry.servers.length > 0 ||
- !mountEntry.servesMountTable;
- var hasMountPoint = mountEntry.servers.length > 0 ||
- mountEntry.servesMountTable;
- var isMounttable = mountEntry.servers.length > 0 &&
- mountEntry.servesMountTable;
+ var hasServers = (mountEntry.servers && mountEntry.servers.length > 0);
+
+ var hasServer = hasServers || !mountEntry.servesMountTable;
+ var hasMountPoint = hasServers || mountEntry.servesMountTable;
+ var isMounttable = hasServers && mountEntry.servesMountTable;
var item = itemFactory.createItem({
objectName: name,
@@ -399,8 +517,8 @@
/*
* Given an arbitrary Vanadium name, parses it into an array
* of strings.
- * For example, if name is "/ns.dev.v.io:8101/global/rps"
- * returns ["ns.dev.v.io:8101", "global", "rps"]
+ * For example, if name is '/ns.dev.v.io:8101/global/rps'
+ * returns ['ns.dev.v.io:8101', 'global', 'rps']
* Can use namespaceService.util.isRooted to see if the name
* is rooted (begins with a slash).
* Note that the address part can contain slashes.
diff --git a/src/vanadium-config.js b/src/vanadium-config.js
deleted file mode 100644
index 04d6d21..0000000
--- a/src/vanadium-config.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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 logLevels = require('vanadium').vlog.levels;
-var vanadiumConfig = {
- logLevel: logLevels.INFO
-};
-
-module.exports = vanadiumConfig;