github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/jsribosome.go (about)

     1  // Copyright (C) 2013-2018, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.)
     2  // Use of this source code is governed by GPLv3 found in the LICENSE file
     3  //----------------------------------------------------------------------------------------
     4  // JSRibosome implements a javascript use of the Ribosome interface
     5  
     6  package holochain
     7  
     8  import (
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	. "github.com/holochain/holochain-proto/hash"
    13  	peer "github.com/libp2p/go-libp2p-peer"
    14  	"github.com/robertkrimen/otto"
    15  	"strings"
    16  	"time"
    17  )
    18  
    19  const (
    20  	JSRibosomeType = "js"
    21  
    22  	ErrHandlingReturnErrorsStr = "returnErrorValue"
    23  	ErrHandlingThrowErrorsStr  = "throwErrors"
    24  )
    25  
    26  // JSRibosome holds data needed for the Javascript VM
    27  type JSRibosome struct {
    28  	h          *Holochain
    29  	zome       *Zome
    30  	vm         *otto.Otto
    31  	lastResult *otto.Value
    32  }
    33  
    34  // Type returns the string value under which this ribosome is registered
    35  func (jsr *JSRibosome) Type() string { return JSRibosomeType }
    36  
    37  // ChainGenesis runs the application genesis function
    38  // this function gets called after the genesis entries are added to the chain
    39  func (jsr *JSRibosome) ChainGenesis() (err error) {
    40  	err = jsr.boolFn("genesis", "")
    41  	return
    42  }
    43  
    44  // BridgeGenesis runs the bridging genesis function
    45  // this function gets called on both sides of the bridging
    46  func (jsr *JSRibosome) BridgeGenesis(side int, dnaHash Hash, data string) (err error) {
    47  	err = jsr.boolFn("bridgeGenesis", fmt.Sprintf(`%d,"%s","%s"`, side, dnaHash.String(), jsSanitizeString(data)))
    48  	return
    49  }
    50  
    51  func (jsr *JSRibosome) boolFn(fnName string, args string) (err error) {
    52  	var v otto.Value
    53  	v, err = jsr.vm.Run(fnName + "(" + args + ")")
    54  
    55  	if err != nil {
    56  		err = fmt.Errorf("Error executing %s: %v", fnName, err)
    57  		return
    58  	}
    59  	if v.IsBoolean() {
    60  		var b bool
    61  		b, err = v.ToBoolean()
    62  		if err != nil {
    63  			return
    64  		}
    65  		if !b {
    66  			err = fmt.Errorf("%s failed", fnName)
    67  		}
    68  
    69  	} else {
    70  		err = fmt.Errorf("%s should return boolean, got: %v", fnName, v)
    71  	}
    72  	return
    73  }
    74  
    75  // Receive calls the app receive function for node-to-node messages
    76  func (jsr *JSRibosome) Receive(from string, msg string) (response string, err error) {
    77  	var code string
    78  	fnName := "receive"
    79  
    80  	code = fmt.Sprintf(`JSON.stringify(%s("%s",JSON.parse("%s")))`, fnName, from, jsSanitizeString(msg))
    81  	jsr.h.Debug(code)
    82  	var v otto.Value
    83  	v, err = jsr.vm.Run(code)
    84  	if err != nil {
    85  		err = fmt.Errorf("Error executing %s: %v", fnName, err)
    86  		return
    87  	}
    88  	response, err = v.ToString()
    89  	return
    90  }
    91  
    92  // BundleCancel calls the app bundleCanceled function
    93  func (jsr *JSRibosome) BundleCanceled(reason string) (response string, err error) {
    94  	var code string
    95  	fnName := "bundleCanceled"
    96  	bundle := jsr.h.chain.BundleStarted()
    97  	if bundle == nil {
    98  		err = ErrBundleNotStarted
    99  		return
   100  	}
   101  
   102  	code = fmt.Sprintf(`%s("%s",JSON.parse("%s"))`, fnName, jsSanitizeString(reason), jsSanitizeString(bundle.userParam))
   103  	jsr.h.Debug(code)
   104  	var v otto.Value
   105  	v, err = jsr.vm.Run(code)
   106  	if err != nil {
   107  		err = fmt.Errorf("Error executing %s: %v", fnName, err)
   108  		return
   109  	}
   110  	response, err = v.ToString()
   111  	return
   112  }
   113  
   114  // ValidatePackagingRequest calls the app for a validation packaging request for an action
   115  func (jsr *JSRibosome) ValidatePackagingRequest(action ValidatingAction, def *EntryDef) (req PackagingReq, err error) {
   116  	var code string
   117  	fnName := "validate" + strings.Title(action.Name()) + "Pkg"
   118  	code = fmt.Sprintf(`%s("%s")`, fnName, def.Name)
   119  	jsr.h.Debug(code)
   120  	var v otto.Value
   121  	v, err = jsr.vm.Run(code)
   122  	if err != nil {
   123  		err = fmt.Errorf("Error executing %s: %v", fnName, err)
   124  		return
   125  	}
   126  	if v.IsObject() {
   127  		var m interface{}
   128  		m, err = v.Export()
   129  		if err != nil {
   130  			return
   131  		}
   132  		req = m.(map[string]interface{})
   133  	} else if v.IsNull() {
   134  
   135  	} else {
   136  		err = fmt.Errorf("%s should return null or object, got: %v", fnName, v)
   137  	}
   138  	return
   139  }
   140  
   141  func prepareJSEntryArgs(def *EntryDef, entry Entry, header *Header) (args string, err error) {
   142  	entryStr := entry.Content().(string)
   143  	switch def.DataFormat {
   144  	case DataFormatRawJS:
   145  		args = entryStr
   146  	case DataFormatString:
   147  		args = "\"" + jsSanitizeString(entryStr) + "\""
   148  	case DataFormatLinks:
   149  		fallthrough
   150  	case DataFormatJSON:
   151  		args = fmt.Sprintf(`JSON.parse("%s")`, jsSanitizeString(entryStr))
   152  	default:
   153  		err = errors.New("data format not implemented: " + def.DataFormat)
   154  		return
   155  	}
   156  	var hdr string
   157  	if header != nil {
   158  		hdr = fmt.Sprintf(
   159  			`{"EntryLink":"%s","Type":"%s","Time":"%s"}`,
   160  			header.EntryLink.String(),
   161  			header.Type,
   162  			header.Time.UTC().Format(time.RFC3339),
   163  		)
   164  	} else {
   165  		hdr = `{"EntryLink":"","Type":"","Time":""}`
   166  	}
   167  	args += "," + hdr
   168  	return
   169  }
   170  
   171  func prepareJSValidateArgs(action Action, def *EntryDef) (args string, err error) {
   172  	switch t := action.(type) {
   173  	case *ActionPut:
   174  		args, err = prepareJSEntryArgs(def, t.entry, t.header)
   175  	case *ActionCommit:
   176  		args, err = prepareJSEntryArgs(def, t.entry, t.header)
   177  	case *ActionMod:
   178  		args, err = prepareJSEntryArgs(def, t.entry, t.header)
   179  		if err == nil {
   180  			args += fmt.Sprintf(`,"%s"`, t.replaces.String())
   181  		}
   182  	case *ActionDel:
   183  		args = fmt.Sprintf(`"%s"`, t.entry.Hash.String())
   184  	case *ActionLink:
   185  		var j []byte
   186  		j, err = json.Marshal(t.links)
   187  		if err == nil {
   188  			args = fmt.Sprintf(`"%s",JSON.parse("%s")`, t.validationBase.String(), jsSanitizeString(string(j)))
   189  		}
   190  	default:
   191  		err = fmt.Errorf("can't prepare args for %T: ", t)
   192  		return
   193  	}
   194  	return
   195  }
   196  
   197  func buildJSValidateAction(action Action, def *EntryDef, pkg *ValidationPackage, sources []string) (code string, err error) {
   198  	fnName := "validate" + strings.Title(action.Name())
   199  	var args string
   200  	args, err = prepareJSValidateArgs(action, def)
   201  	if err != nil {
   202  		return
   203  	}
   204  	srcs := mkJSSources(sources)
   205  
   206  	var pkgObj string
   207  	if pkg == nil || pkg.Chain == nil {
   208  		pkgObj = "{}"
   209  	} else {
   210  		var j []byte
   211  		j, err = json.Marshal(pkg.Chain)
   212  		if err != nil {
   213  			return
   214  		}
   215  		pkgObj = fmt.Sprintf(`{"Chain":%s}`, j)
   216  	}
   217  	code = fmt.Sprintf(`%s("%s",%s,%s,%s)`, fnName, def.Name, args, pkgObj, srcs)
   218  
   219  	return
   220  }
   221  
   222  // ValidateAction builds the correct validation function based on the action an calls it
   223  func (jsr *JSRibosome) ValidateAction(action Action, def *EntryDef, pkg *ValidationPackage, sources []string) (err error) {
   224  	var code string
   225  	code, err = buildJSValidateAction(action, def, pkg, sources)
   226  	if err != nil {
   227  		return
   228  	}
   229  	jsr.h.Debug(code)
   230  	err = jsr.runValidate(action.Name(), code)
   231  	return
   232  }
   233  
   234  func mkJSSources(sources []string) (srcs string) {
   235  	srcs = `["` + strings.Join(sources, `","`) + `"]`
   236  	return
   237  }
   238  
   239  func (jsr *JSRibosome) prepareJSValidateEntryArgs(def *EntryDef, entry Entry, sources []string) (e string, srcs string, err error) {
   240  	c := entry.Content().(string)
   241  	switch def.DataFormat {
   242  	case DataFormatRawJS:
   243  		e = c
   244  	case DataFormatString:
   245  		e = "\"" + jsSanitizeString(c) + "\""
   246  	case DataFormatLinks:
   247  		fallthrough
   248  	case DataFormatJSON:
   249  		e = fmt.Sprintf(`JSON.parse("%s")`, jsSanitizeString(c))
   250  	default:
   251  		err = errors.New("data format not implemented: " + def.DataFormat)
   252  		return
   253  	}
   254  	srcs = mkJSSources(sources)
   255  	return
   256  }
   257  
   258  func (jsr *JSRibosome) runValidate(fnName string, code string) (err error) {
   259  	var v otto.Value
   260  	v, err = jsr.vm.Run(code)
   261  	if err != nil {
   262  		err = fmt.Errorf("Error executing %s: %v", fnName, err)
   263  		return
   264  	}
   265  	if v.IsBoolean() {
   266  		var b bool
   267  		b, err = v.ToBoolean()
   268  		if err != nil {
   269  			return
   270  		}
   271  		if !b {
   272  			err = ValidationFailed()
   273  		}
   274  	} else if v.IsString() {
   275  		var s string
   276  		s, err = v.ToString()
   277  		if err != nil {
   278  			return
   279  		}
   280  		if s != "" {
   281  			err = ValidationFailed(s)
   282  		}
   283  
   284  	} else {
   285  		err = fmt.Errorf("%s should return boolean or string, got: %v", fnName, v)
   286  	}
   287  	return
   288  }
   289  
   290  func (jsr *JSRibosome) validateEntry(fnName string, def *EntryDef, entry Entry, header *Header, sources []string) (err error) {
   291  
   292  	e, srcs, err := jsr.prepareJSValidateEntryArgs(def, entry, sources)
   293  	if err != nil {
   294  		return
   295  	}
   296  
   297  	hdr := fmt.Sprintf(
   298  		`{"EntryLink":"%s","Type":"%s","Time":"%s"}`,
   299  		header.EntryLink.String(),
   300  		header.Type,
   301  		header.Time.UTC().Format(time.RFC3339),
   302  	)
   303  
   304  	code := fmt.Sprintf(`%s("%s",%s,%s,%s)`, fnName, def.Name, e, hdr, srcs)
   305  	jsr.h.Debugf("%s: %s", fnName, code)
   306  	err = jsr.runValidate(fnName, code)
   307  	return
   308  }
   309  
   310  const (
   311  	JSLibrary = `var HC={Version:` + `"` + VersionStr + "\"" +
   312  		`SysEntryType:{` +
   313  		`DNA:"` + DNAEntryType + `",` +
   314  		`Agent:"` + AgentEntryType + `",` +
   315  		`Key:"` + KeyEntryType + `",` +
   316  		`Headers:"` + HeadersEntryType + `"` +
   317  		`Del:"` + DelEntryType + `"` +
   318  		`Migrate:"` + MigrateEntryType + `"` +
   319  		`}` +
   320  		`HashNotFound:null` +
   321  		`,Status:{Live:` + StatusLiveVal +
   322  		`,Rejected:` + StatusRejectedVal +
   323  		`,Deleted:` + StatusDeletedVal +
   324  		`,Modified:` + StatusModifiedVal +
   325  		`,Any:` + StatusAnyVal +
   326  		"}" +
   327  		`,GetMask:{Default:` + GetMaskDefaultStr +
   328  		`,Entry:` + GetMaskEntryStr +
   329  		`,EntryType:` + GetMaskEntryTypeStr +
   330  		`,Sources:` + GetMaskSourcesStr +
   331  		`,All:` + GetMaskAllStr +
   332  		"}" +
   333  		`,LinkAction:{Add:"` + AddLinkAction + `",Del:"` + DelLinkAction + `"}` +
   334  		`,PkgReq:{Chain:"` + PkgReqChain + `"` +
   335  		`,ChainOpt:{None:` + PkgReqChainOptNoneStr +
   336  		`,Headers:` + PkgReqChainOptHeadersStr +
   337  		`,Entries:` + PkgReqChainOptEntriesStr +
   338  		`,Full:` + PkgReqChainOptFullStr +
   339  		"}" +
   340  		"}" +
   341  		`,Bridge:{Caller:` + BridgeCallerStr +
   342  		`,Callee:` + BridgeCalleeStr +
   343  		"}" +
   344  		`,BundleCancel:{` +
   345  		`Reason:{UserCancel:"` + BundleCancelReasonUserCancel +
   346  		`",Timeout:"` + BundleCancelReasonTimeout +
   347  		`"},Response:{OK:"` + BundleCancelResponseOK +
   348  		`",Commit:"` + BundleCancelResponseCommit +
   349  		`"}}` +
   350  		`Migrate:{Close:"` + MigrateEntryTypeClose + `",Open:"` + MigrateEntryTypeOpen + `"}` +
   351  		`};`
   352  )
   353  
   354  // jsSanatizeString makes sure all quotes are quoted and returns are removed
   355  func jsSanitizeString(s string) string {
   356  	s = strings.Replace(s, `\`, "%%%slash%%%", -1)
   357  	s = strings.Replace(s, "\n", "\\n", -1)
   358  	s = strings.Replace(s, "\t", "\\t", -1)
   359  	s = strings.Replace(s, "\r", "", -1)
   360  	s = strings.Replace(s, "\"", "\\\"", -1)
   361  	s = strings.Replace(s, "%%%slash%%%", `\\`, -1)
   362  	return s
   363  }
   364  
   365  // Call calls the zygo function that was registered with expose
   366  func (jsr *JSRibosome) Call(fn *FunctionDef, params interface{}) (result interface{}, err error) {
   367  	var code string
   368  	switch fn.CallingType {
   369  	case STRING_CALLING:
   370  		code = fmt.Sprintf(`%s("%s");`, fn.Name, jsSanitizeString(params.(string)))
   371  	case JSON_CALLING:
   372  		if params.(string) == "" {
   373  			code = fmt.Sprintf(`JSON.stringify(%s());`, fn.Name)
   374  		} else {
   375  			p := jsSanitizeString(params.(string))
   376  			code = fmt.Sprintf(`JSON.stringify(%s(JSON.parse("%s")));`, fn.Name, p)
   377  		}
   378  	default:
   379  		err = errors.New("params type not implemented")
   380  		return
   381  	}
   382  	jsr.h.Debugf("JS Call: %s", code)
   383  	var v otto.Value
   384  	v, err = jsr.vm.Run(code)
   385  	if err == nil {
   386  		if v.IsObject() && v.Class() == "Error" {
   387  			jsr.h.Debugf("JS Error:\n%v", v)
   388  			var message otto.Value
   389  			message, err = v.Object().Get("message")
   390  			if err == nil {
   391  				err = errors.New(message.String())
   392  			}
   393  		} else {
   394  			result, err = v.ToString()
   395  		}
   396  	}
   397  	return
   398  }
   399  
   400  // jsProcessArgs processes oArgs according to the args spec filling args[].value with the converted value
   401  func jsProcessArgs(jsr *JSRibosome, args []Arg, oArgs []otto.Value) (err error) {
   402  	err = checkArgCount(args, len(oArgs))
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	// check arg types
   408  	for i, arg := range oArgs {
   409  		if arg.IsUndefined() && args[i].Optional {
   410  			return
   411  		}
   412  		switch args[i].Type {
   413  		case StringArg:
   414  			if arg.IsString() {
   415  				args[i].value, _ = arg.ToString()
   416  			} else {
   417  				return argErr("string", i+1, args[i])
   418  			}
   419  		case HashArg:
   420  			if arg.IsString() {
   421  				str, _ := arg.ToString()
   422  				var hash Hash
   423  				hash, err = NewHash(str)
   424  				if err != nil {
   425  					return
   426  				}
   427  				args[i].value = hash
   428  			} else {
   429  				return argErr("string", i+1, args[i])
   430  			}
   431  		case IntArg:
   432  			if arg.IsNumber() {
   433  				integer, err := arg.ToInteger()
   434  				if err != nil {
   435  					return err
   436  				}
   437  				args[i].value = integer
   438  			} else {
   439  				return argErr("int", i+1, args[i])
   440  			}
   441  		case BoolArg:
   442  			if arg.IsBoolean() {
   443  				boolean, err := arg.ToBoolean()
   444  				if err != nil {
   445  					return err
   446  				}
   447  				args[i].value = boolean
   448  			} else {
   449  				return argErr("boolean", i+1, args[i])
   450  			}
   451  		case ArgsArg:
   452  			if arg.IsString() {
   453  				str, err := arg.ToString()
   454  				if err != nil {
   455  					return err
   456  				}
   457  				args[i].value = str
   458  			} else if arg.IsObject() {
   459  				v, err := jsr.vm.Call("JSON.stringify", nil, arg)
   460  				if err != nil {
   461  					return err
   462  				}
   463  				entry, err := v.ToString()
   464  				if err != nil {
   465  					return err
   466  				}
   467  				args[i].value = entry
   468  
   469  			} else {
   470  				return argErr("string or object", i+1, args[i])
   471  			}
   472  		case EntryArg:
   473  			// this a special case in that all EntryArgs must be preceeded by
   474  			// string arg that specifies the entry type
   475  			entryType, err := oArgs[i-1].ToString()
   476  			if err != nil {
   477  				return err
   478  			}
   479  			_, def, err := jsr.h.GetEntryDef(entryType)
   480  			if err != nil {
   481  				return err
   482  			}
   483  			var entry string
   484  			switch def.DataFormat {
   485  			case DataFormatRawJS:
   486  				fallthrough
   487  			case DataFormatRawZygo:
   488  				fallthrough
   489  			case DataFormatString:
   490  				if !arg.IsString() {
   491  					return argErr("string", i+1, args[i])
   492  				}
   493  				entry, err = arg.ToString()
   494  				if err != nil {
   495  					return err
   496  				}
   497  			case DataFormatLinks:
   498  				if !arg.IsObject() {
   499  					return argErr("object", i+1, args[i])
   500  				}
   501  				fallthrough
   502  			case DataFormatJSON:
   503  				v, err := jsr.vm.Call("JSON.stringify", nil, arg)
   504  				if err != nil {
   505  					return err
   506  				}
   507  				entry, err = v.ToString()
   508  				if err != nil {
   509  					return err
   510  				}
   511  			default:
   512  				err = errors.New("data format not implemented: " + def.DataFormat)
   513  				return err
   514  			}
   515  
   516  			args[i].value = entry
   517  		case MapArg:
   518  			if arg.IsObject() {
   519  				m, err := arg.Export()
   520  				if err != nil {
   521  					return err
   522  				}
   523  				args[i].value = m
   524  			} else {
   525  				return argErr("object", i+1, args[i])
   526  			}
   527  		case ToStrArg:
   528  			var str string
   529  			if arg.IsObject() {
   530  				v, err := jsr.vm.Call("JSON.stringify", nil, arg)
   531  				if err != nil {
   532  					return err
   533  				}
   534  				str, err = v.ToString()
   535  				if err != nil {
   536  					return err
   537  				}
   538  			} else {
   539  				str, _ = arg.ToString()
   540  			}
   541  			args[i].value = str
   542  		}
   543  	}
   544  	return
   545  }
   546  
   547  const (
   548  	HolochainErrorPrefix = "HolochainError"
   549  )
   550  
   551  func mkOttoErr(jsr *JSRibosome, msg string) otto.Value {
   552  	return jsr.vm.MakeCustomError(HolochainErrorPrefix, msg)
   553  }
   554  
   555  func numInterfaceToInt(num interface{}) (val int, ok bool) {
   556  	ok = true
   557  	switch t := num.(type) {
   558  	case int64:
   559  		val = int(t)
   560  	case float64:
   561  		val = int(t)
   562  	case int:
   563  		val = t
   564  	default:
   565  		ok = false
   566  	}
   567  	return
   568  }
   569  
   570  type fnData struct {
   571  	apiFn APIFunction
   572  	f     func([]Arg, APIFunction, otto.FunctionCall) (otto.Value, error)
   573  }
   574  
   575  func makeOttoObjectFromGetResp(h *Holochain, jsr *JSRibosome, getResp *GetResp) (result interface{}, err error) {
   576  	_, def, err := h.GetEntryDef(getResp.EntryType)
   577  	if err != nil {
   578  		return
   579  	}
   580  	if def.DataFormat == DataFormatJSON {
   581  		json := getResp.Entry.Content().(string)
   582  		code := `(` + json + `)`
   583  		result, err = jsr.vm.Object(code)
   584  	} else {
   585  		result = getResp.Entry.Content().(string)
   586  	}
   587  	return
   588  }
   589  
   590  // NewJSRibosome factory function to build a javascript execution environment for a zome
   591  func NewJSRibosome(h *Holochain, zome *Zome) (n Ribosome, err error) {
   592  	jsr := JSRibosome{
   593  		h:    h,
   594  		zome: zome,
   595  		vm:   otto.New(),
   596  	}
   597  
   598  	funcs := map[string]fnData{
   599  		"property": fnData{
   600  			apiFn: &APIFnProperty{},
   601  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   602  				f := _f.(*APIFnProperty)
   603  				f.prop = args[0].value.(string)
   604  
   605  				var p interface{}
   606  				p, err = f.Call(h)
   607  				if err != nil {
   608  					return otto.UndefinedValue(), nil
   609  				}
   610  				result, err = jsr.vm.ToValue(p)
   611  				return
   612  			},
   613  		},
   614  		"debug": fnData{
   615  			apiFn: &APIFnDebug{},
   616  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   617  				f := _f.(*APIFnDebug)
   618  				f.msg = args[0].value.(string)
   619  				f.Call(h)
   620  				return otto.UndefinedValue(), nil
   621  			},
   622  		},
   623  		"makeHash": fnData{
   624  			apiFn: &APIFnMakeHash{},
   625  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   626  				f := _f.(*APIFnMakeHash)
   627  				f.entryType = args[0].value.(string)
   628  				f.entry = &GobEntry{C: args[1].value.(string)}
   629  				var r interface{}
   630  				r, err = f.Call(h)
   631  				if err != nil {
   632  					return
   633  				}
   634  				var entryHash Hash
   635  				if r != nil {
   636  					entryHash = r.(Hash)
   637  				}
   638  				result, _ = jsr.vm.ToValue(entryHash.String())
   639  				return result, nil
   640  			},
   641  		},
   642  		"getBridges": fnData{
   643  			apiFn: &APIFnGetBridges{},
   644  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   645  				f := _f.(*APIFnGetBridges)
   646  				var r interface{}
   647  				r, err = f.Call(h)
   648  				if err != nil {
   649  					return
   650  				}
   651  				var code string
   652  				for i, b := range r.([]Bridge) {
   653  					if i > 0 {
   654  						code += ","
   655  					}
   656  					if b.Side == BridgeCallee {
   657  						code += fmt.Sprintf(`{Side:%d,Token:"%s"}`, b.Side, b.Token)
   658  					} else {
   659  						code += fmt.Sprintf(`{Side:%d,CalleeApp:"%s",CalleeName:"%s"}`, b.Side, b.CalleeApp.String(), b.CalleeName)
   660  					}
   661  				}
   662  				code = "[" + code + "]"
   663  				object, _ := jsr.vm.Object(code)
   664  				result, _ = jsr.vm.ToValue(object)
   665  				return
   666  			},
   667  		},
   668  		"sign": fnData{
   669  			apiFn: &APIFnSign{},
   670  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   671  				f := _f.(*APIFnSign)
   672  				f.data = []byte(args[0].value.(string))
   673  				var r interface{}
   674  				r, err = f.Call(h)
   675  				if err != nil {
   676  					return
   677  				}
   678  				var b58sig string
   679  				if r != nil {
   680  					b58sig = r.(string)
   681  				}
   682  				result, _ = jsr.vm.ToValue(b58sig)
   683  				return
   684  			},
   685  		},
   686  		"verifySignature": fnData{
   687  			apiFn: &APIFnVerifySignature{},
   688  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   689  				f := _f.(*APIFnVerifySignature)
   690  				f.b58signature = args[0].value.(string)
   691  				f.data = args[1].value.(string)
   692  				f.b58pubKey = args[2].value.(string)
   693  				var r interface{}
   694  				r, err = f.Call(h)
   695  				if err != nil {
   696  					return
   697  				}
   698  				result, err = jsr.vm.ToValue(r)
   699  				if err != nil {
   700  					return
   701  				}
   702  				return
   703  			},
   704  		},
   705  		"send": fnData{
   706  			apiFn: &APIFnSend{},
   707  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   708  				f := _f.(*APIFnSend)
   709  				a := &f.action
   710  				a.to, err = peer.IDB58Decode(args[0].value.(Hash).String())
   711  				if err != nil {
   712  					return
   713  				}
   714  				msg := args[1].value.(map[string]interface{})
   715  				var j []byte
   716  				j, err = json.Marshal(msg)
   717  				if err != nil {
   718  					return
   719  				}
   720  
   721  				a.msg.ZomeType = jsr.zome.Name
   722  				a.msg.Body = string(j)
   723  
   724  				if args[2].value != nil {
   725  					a.options = &SendOptions{}
   726  					opts := args[2].value.(map[string]interface{})
   727  					cbmap, ok := opts["Callback"]
   728  					if ok {
   729  						callback := Callback{zomeType: zome.Name}
   730  						v, ok := cbmap.(map[string]interface{})["Function"]
   731  						if !ok {
   732  							err = errors.New("callback option requires Function")
   733  							return
   734  						}
   735  						callback.Function = v.(string)
   736  						v, ok = cbmap.(map[string]interface{})["ID"]
   737  						if !ok {
   738  							err = errors.New("callback option requires ID")
   739  							return
   740  						}
   741  						callback.ID = v.(string)
   742  						a.options.Callback = &callback
   743  					}
   744  					timeout, ok := opts["Timeout"]
   745  					if ok {
   746  						a.options.Timeout = int(timeout.(int64))
   747  					}
   748  				}
   749  
   750  				var r interface{}
   751  				r, err = f.Call(h)
   752  				if err != nil {
   753  					return
   754  				}
   755  				result, err = jsr.vm.ToValue(r)
   756  				return
   757  			},
   758  		},
   759  		"call": fnData{
   760  			apiFn: &APIFnCall{},
   761  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   762  				f := _f.(*APIFnCall)
   763  				f.zome = args[0].value.(string)
   764  				var zome *Zome
   765  				zome, err = h.GetZome(f.zome)
   766  				if err != nil {
   767  					return
   768  				}
   769  				f.function = args[1].value.(string)
   770  				var fn *FunctionDef
   771  				fn, err = zome.GetFunctionDef(f.function)
   772  				if err != nil {
   773  					return
   774  				}
   775  				if fn.CallingType == JSON_CALLING {
   776  					/* this is a mistake.
   777  					if !call.ArgumentList[2].IsObject() {
   778  								return mkOttoErr(&jsr, "function calling type requires object argument type")
   779  							}*/
   780  				}
   781  				f.args = args[2].value.(string)
   782  
   783  				var r interface{}
   784  				r, err = f.Call(h)
   785  				if err != nil {
   786  					return
   787  				}
   788  
   789  				result, err = jsr.vm.ToValue(r)
   790  				return
   791  			},
   792  		},
   793  		"bridge": fnData{
   794  			apiFn: &APIFnBridge{},
   795  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   796  				f := _f.(*APIFnBridge)
   797  				hash := args[0].value.(Hash)
   798  				f.token, f.url, err = h.GetBridgeToken(hash)
   799  				if err != nil {
   800  					return
   801  				}
   802  
   803  				f.zome = args[1].value.(string)
   804  				f.function = args[2].value.(string)
   805  				f.args = args[3].value.(string)
   806  
   807  				var r interface{}
   808  				r, err = f.Call(h)
   809  				if err != nil {
   810  					return
   811  				}
   812  				result, err = jsr.vm.ToValue(r)
   813  				return
   814  			},
   815  		},
   816  		"commit": fnData{
   817  			apiFn: &APIFnCommit{},
   818  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   819  				f := _f.(*APIFnCommit)
   820  				entryType := args[0].value.(string)
   821  				entryStr := args[1].value.(string)
   822  				var r interface{}
   823  				entry := GobEntry{C: entryStr}
   824  				f.action.entryType = entryType
   825  				f.action.entry = &entry
   826  				r, err = f.Call(h)
   827  				if err != nil {
   828  					return
   829  				}
   830  				var entryHash Hash
   831  				if r != nil {
   832  					entryHash = r.(Hash)
   833  				}
   834  
   835  				result, err = jsr.vm.ToValue(entryHash.String())
   836  				return
   837  			},
   838  		},
   839  		"migrate": fnData{
   840  			apiFn: &APIFnMigrate{},
   841  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   842  				f := _f.(*APIFnMigrate)
   843  				migrationType := args[0].value.(string)
   844  				DNAHash := args[1].value.(Hash)
   845  				Key := args[2].value.(Hash)
   846  				Data := args[3].value.(string)
   847  				var r interface{}
   848  				f.action.entry.Type = migrationType
   849  				f.action.entry.DNAHash = DNAHash
   850  				f.action.entry.Key = Key
   851  				f.action.entry.Data = Data
   852  				r, err = f.Call(h)
   853  				if err != nil {
   854  					return
   855  				}
   856  				var entryHash Hash
   857  				if r != nil {
   858  					entryHash = r.(Hash)
   859  				}
   860  
   861  				result, err = jsr.vm.ToValue(entryHash.String())
   862  				return
   863  			},
   864  		},
   865  
   866  		"query": fnData{
   867  			apiFn: &APIFnQuery{},
   868  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   869  				f := _f.(*APIFnQuery)
   870  				if len(call.ArgumentList) == 1 {
   871  					options := QueryOptions{}
   872  					var j []byte
   873  					j, err = json.Marshal(args[0].value)
   874  					if err != nil {
   875  						return
   876  					}
   877  					jsr.h.Debugf("Query options: %s", string(j))
   878  					err = json.Unmarshal(j, &options)
   879  					if err != nil {
   880  						return
   881  					}
   882  					f.options = &options
   883  				}
   884  				var r interface{}
   885  				r, err = f.Call(h)
   886  				if err != nil {
   887  					return
   888  				}
   889  				qr := r.([]QueryResult)
   890  
   891  				defs := make(map[string]*EntryDef)
   892  				var code string
   893  				for i, qresult := range qr {
   894  					if i > 0 {
   895  						code += ","
   896  					}
   897  					var entryCode, hashCode, headerCode string
   898  					var returnCount int
   899  					if f.options.Return.Hashes {
   900  						returnCount += 1
   901  						hashCode = `"` + qresult.Header.EntryLink.String() + `"`
   902  					}
   903  					if f.options.Return.Headers {
   904  						returnCount += 1
   905  						headerCode, err = qresult.Header.ToJSON()
   906  						if err != nil {
   907  							return
   908  						}
   909  					}
   910  					if f.options.Return.Entries {
   911  						returnCount += 1
   912  
   913  						var def *EntryDef
   914  						var ok bool
   915  						def, ok = defs[qresult.Header.Type]
   916  						if !ok {
   917  							_, def, err = h.GetEntryDef(qresult.Header.Type)
   918  							if err != nil {
   919  								return
   920  							}
   921  							defs[qresult.Header.Type] = def
   922  						}
   923  						r := qresult.Entry.Content()
   924  						switch def.DataFormat {
   925  						case DataFormatRawJS:
   926  							entryCode = r.(string)
   927  						case DataFormatString:
   928  							entryCode = fmt.Sprintf(`"%s"`, jsSanitizeString(r.(string)))
   929  						case DataFormatLinks:
   930  							fallthrough
   931  						case DataFormatJSON:
   932  							entryCode = fmt.Sprintf(`JSON.parse("%s")`, jsSanitizeString(r.(string)))
   933  						default:
   934  							err = errors.New("data format not implemented: " + def.DataFormat)
   935  							return
   936  						}
   937  					}
   938  					if returnCount == 1 {
   939  						code += entryCode + hashCode + headerCode
   940  					} else {
   941  						var c string
   942  						if entryCode != "" {
   943  							c += "Entry:" + entryCode
   944  						}
   945  						if hashCode != "" {
   946  							if c != "" {
   947  								c += ","
   948  							}
   949  							c += "Hash:" + hashCode
   950  						}
   951  						if headerCode != "" {
   952  							if c != "" {
   953  								c += ","
   954  							}
   955  							c += "Header:" + headerCode
   956  						}
   957  						code += "{" + c + "}"
   958  					}
   959  
   960  				}
   961  				code = "[" + code + "]"
   962  				jsr.h.Debugf("Query Code:%s\n", code)
   963  				object, _ := jsr.vm.Object(code)
   964  				result, err = jsr.vm.ToValue(object)
   965  				return
   966  			},
   967  		},
   968  		"get": fnData{
   969  			apiFn: &APIFnGet{},
   970  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
   971  				f := _f.(*APIFnGet)
   972  				options := GetOptions{StatusMask: StatusDefault}
   973  				if len(call.ArgumentList) == 2 {
   974  					opts, ok := args[1].value.(map[string]interface{})
   975  					if ok {
   976  						mask, ok := opts["StatusMask"]
   977  						if ok {
   978  							// otto returns int64 or float64 depending on whether
   979  							// the mask was returned by constant or addition so
   980  							maskval, ok := numInterfaceToInt(mask)
   981  							if !ok {
   982  								err = errors.New(fmt.Sprintf("expecting int StatusMask attribute, got %T", mask))
   983  								return
   984  							}
   985  							options.StatusMask = int(maskval)
   986  						}
   987  						mask, ok = opts["GetMask"]
   988  						if ok {
   989  							maskval, ok := numInterfaceToInt(mask)
   990  							if !ok {
   991  								err = errors.New(fmt.Sprintf("expecting int GetMask attribute, got %T", mask))
   992  								return
   993  							}
   994  							options.GetMask = int(maskval)
   995  						}
   996  						local, ok := opts["Local"]
   997  						if ok {
   998  							options.Local = local.(bool)
   999  						}
  1000  					}
  1001  				}
  1002  				req := GetReq{H: args[0].value.(Hash), StatusMask: options.StatusMask, GetMask: options.GetMask}
  1003  				var r interface{}
  1004  				f.action = ActionGet{req: req, options: &options}
  1005  				r, err = f.Call(h)
  1006  				mask := options.GetMask
  1007  				if mask == GetMaskDefault {
  1008  					mask = GetMaskEntry
  1009  				}
  1010  				if err == ErrHashNotFound {
  1011  					// if the hash wasn't found this isn't actually an error
  1012  					// so return nil which is the same as HC.HashNotFound
  1013  					err = nil
  1014  					result = otto.NullValue()
  1015  				} else if err == nil {
  1016  					getResp := r.(GetResp)
  1017  					var singleValueReturn bool
  1018  					if mask&GetMaskEntry != 0 {
  1019  						if GetMaskEntry == mask {
  1020  							singleValueReturn = true
  1021  							var entry interface{}
  1022  							entry, err = makeOttoObjectFromGetResp(h, &jsr, &getResp)
  1023  							if err != nil {
  1024  								return
  1025  							}
  1026  							result, err = jsr.vm.ToValue(entry)
  1027  						}
  1028  					}
  1029  					if mask&GetMaskEntryType != 0 {
  1030  						if GetMaskEntryType == mask {
  1031  							singleValueReturn = true
  1032  							result, err = jsr.vm.ToValue(getResp.EntryType)
  1033  						}
  1034  					}
  1035  					if mask&GetMaskSources != 0 {
  1036  						if GetMaskSources == mask {
  1037  							singleValueReturn = true
  1038  							result, err = jsr.vm.ToValue(getResp.Sources)
  1039  						}
  1040  					}
  1041  					if err == nil && !singleValueReturn {
  1042  						respObj := make(map[string]interface{})
  1043  						if mask&GetMaskEntry != 0 {
  1044  							var entry interface{}
  1045  							entry, err = makeOttoObjectFromGetResp(h, &jsr, &getResp)
  1046  							if err != nil {
  1047  								return
  1048  							}
  1049  							respObj["Entry"] = entry
  1050  						}
  1051  						if mask&GetMaskEntryType != 0 {
  1052  							respObj["EntryType"] = getResp.EntryType
  1053  						}
  1054  						if mask&GetMaskSources != 0 {
  1055  							respObj["Sources"] = getResp.Sources
  1056  						}
  1057  						result, err = jsr.vm.ToValue(respObj)
  1058  					}
  1059  
  1060  				}
  1061  				return
  1062  			},
  1063  		},
  1064  		"update": fnData{
  1065  			apiFn: &APIFnMod{},
  1066  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
  1067  				f := _f.(*APIFnMod)
  1068  
  1069  				entryType := args[0].value.(string)
  1070  				entryStr := args[1].value.(string)
  1071  				replaces := args[2].value.(Hash)
  1072  
  1073  				entry := GobEntry{C: entryStr}
  1074  				f.action = *NewModAction(entryType, &entry, replaces)
  1075  
  1076  				var resp interface{}
  1077  				resp, err = f.Call(h)
  1078  				if err != nil {
  1079  					return
  1080  				}
  1081  				var entryHash Hash
  1082  				if resp != nil {
  1083  					entryHash = resp.(Hash)
  1084  				}
  1085  				result, err = jsr.vm.ToValue(entryHash.String())
  1086  				return
  1087  			},
  1088  		},
  1089  		"updateAgent": fnData{
  1090  			apiFn: &APIFnModAgent{},
  1091  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
  1092  				f := _f.(*APIFnModAgent)
  1093  				opts := args[0].value.(map[string]interface{})
  1094  				id, idok := opts["Identity"]
  1095  				if idok {
  1096  					f.Identity = AgentIdentity(id.(string))
  1097  				}
  1098  				rev, revok := opts["Revocation"]
  1099  				if revok {
  1100  					f.Revocation = rev.(string)
  1101  				}
  1102  				var resp interface{}
  1103  				resp, err = f.Call(h)
  1104  				if err != nil {
  1105  					return
  1106  				}
  1107  				var agentEntryHash Hash
  1108  				if resp != nil {
  1109  					agentEntryHash = resp.(Hash)
  1110  				}
  1111  				if revok {
  1112  					// TODO there should be a better way to set a variable inside that vm.
  1113  					// also worried about the re-entrancy here...
  1114  					_, err = jsr.vm.Run(`App.Key.Hash="` + h.nodeIDStr + `"`)
  1115  					if err != nil {
  1116  						return
  1117  					}
  1118  				}
  1119  
  1120  				// there's always a new agent entry
  1121  				_, err = jsr.vm.Run(`App.Agent.TopHash="` + h.agentTopHash.String() + `"`)
  1122  				if err != nil {
  1123  					return
  1124  				}
  1125  
  1126  				// but not always a new identity to update
  1127  				if idok {
  1128  					_, err = jsr.vm.Run(`App.Agent.String="` + jsSanitizeString(id.(string)) + `"`)
  1129  					if err != nil {
  1130  						return
  1131  					}
  1132  				}
  1133  
  1134  				result, err = jsr.vm.ToValue(agentEntryHash.String())
  1135  
  1136  				return
  1137  			},
  1138  		},
  1139  		"remove": fnData{
  1140  			apiFn: &APIFnDel{},
  1141  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
  1142  				entry := DelEntry{
  1143  					Hash:    args[0].value.(Hash),
  1144  					Message: args[1].value.(string),
  1145  				}
  1146  				var resp interface{}
  1147  				f := _f.(*APIFnDel)
  1148  				f.action = *NewDelAction(entry)
  1149  				resp, err = f.Call(h)
  1150  				if err == nil {
  1151  					var entryHash Hash
  1152  					if resp != nil {
  1153  						entryHash = resp.(Hash)
  1154  					}
  1155  					result, err = jsr.vm.ToValue(entryHash.String())
  1156  				}
  1157  
  1158  				return
  1159  			},
  1160  		},
  1161  		"getLinks": fnData{
  1162  			apiFn: &APIFnGetLinks{},
  1163  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
  1164  				base := args[0].value.(Hash)
  1165  				tag := args[1].value.(string)
  1166  
  1167  				l := len(call.ArgumentList)
  1168  				options := GetLinksOptions{Load: false, StatusMask: StatusLive}
  1169  				if l == 3 {
  1170  					opts, ok := args[2].value.(map[string]interface{})
  1171  					if ok {
  1172  						load, ok := opts["Load"]
  1173  						if ok {
  1174  							loadval, ok := load.(bool)
  1175  							if !ok {
  1176  								err = errors.New(fmt.Sprintf("expecting boolean Load attribute in object, got %T", load))
  1177  								return
  1178  							}
  1179  							options.Load = loadval
  1180  						}
  1181  						mask, ok := opts["StatusMask"]
  1182  						if ok {
  1183  							maskval, ok := numInterfaceToInt(mask)
  1184  							if !ok {
  1185  								err = errors.New(fmt.Sprintf("expecting int StatusMask attribute in object, got %T", mask))
  1186  								return
  1187  							}
  1188  							options.StatusMask = int(maskval)
  1189  						}
  1190  					}
  1191  				}
  1192  				var response interface{}
  1193  				f := _f.(*APIFnGetLinks)
  1194  				f.action = *NewGetLinksAction(&LinkQuery{Base: base, T: tag, StatusMask: options.StatusMask}, &options)
  1195  				response, err = f.Call(h)
  1196  
  1197  				if err == nil {
  1198  					// we build up our response by creating the javascript object
  1199  					// that we want and using otto to create it with vm.
  1200  					// TODO: is there a faster way to do this?
  1201  					lqr := response.(*LinkQueryResp)
  1202  					var js string
  1203  					for i, th := range lqr.Links {
  1204  						var l string
  1205  						l = `Hash:"` + th.H + `"`
  1206  						if tag == "" {
  1207  							l += `,Tag:"` + jsSanitizeString(th.T) + `"`
  1208  						}
  1209  						if options.Load {
  1210  							l += `,EntryType:"` + jsSanitizeString(th.EntryType) + `"`
  1211  							l += `,Source:"` + jsSanitizeString(th.Source) + `"`
  1212  							var def *EntryDef
  1213  							_, def, err = h.GetEntryDef(th.EntryType)
  1214  							if err != nil {
  1215  								break
  1216  							}
  1217  							var entry string
  1218  							switch def.DataFormat {
  1219  							case DataFormatRawJS:
  1220  								entry = th.E
  1221  							case DataFormatRawZygo:
  1222  								fallthrough
  1223  							case DataFormatSysKey:
  1224  								// key is a b58 encoded public key so the entry is just the string value
  1225  								fallthrough
  1226  							case DataFormatString:
  1227  								entry = `"` + jsSanitizeString(th.E) + `"`
  1228  							case DataFormatLinks:
  1229  								fallthrough
  1230  							case DataFormatJSON:
  1231  								entry = `JSON.parse("` + jsSanitizeString(th.E) + `")`
  1232  							default:
  1233  								err = errors.New("data format not implemented: " + def.DataFormat)
  1234  								return
  1235  							}
  1236  
  1237  							l += `,Entry:` + entry
  1238  						}
  1239  						if i > 0 {
  1240  							js += ","
  1241  						}
  1242  						js += `{` + l + `}`
  1243  					}
  1244  					if err == nil {
  1245  						js = `[` + js + `]`
  1246  						var obj *otto.Object
  1247  						jsr.h.Debugf("getLinks code:\n%s", js)
  1248  						obj, err = jsr.vm.Object(js)
  1249  						if err == nil {
  1250  							result = obj.Value()
  1251  						}
  1252  					}
  1253  				}
  1254  				return
  1255  			},
  1256  		},
  1257  		"bundleStart": fnData{
  1258  			apiFn: &APIFnStartBundle{},
  1259  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
  1260  				f := _f.(*APIFnStartBundle)
  1261  				f.timeout = args[0].value.(int64)
  1262  				f.userParam = args[1].value.(string)
  1263  				_, err = f.Call(h)
  1264  				return
  1265  			},
  1266  		},
  1267  		"bundleClose": fnData{
  1268  			apiFn: &APIFnCloseBundle{},
  1269  			f: func(args []Arg, _f APIFunction, call otto.FunctionCall) (result otto.Value, err error) {
  1270  				f := _f.(*APIFnCloseBundle)
  1271  				f.commit = args[0].value.(bool)
  1272  				_, err = f.Call(h)
  1273  				return
  1274  			},
  1275  		},
  1276  	}
  1277  
  1278  	var fnPrefix string
  1279  	var returnErrors bool
  1280  	val, ok := zome.Config["ErrorHandling"]
  1281  	if ok {
  1282  		var errHandling string
  1283  		errHandling, ok = val.(string)
  1284  		if !ok {
  1285  			err = errors.New("Expected ErrorHandling config value to be string")
  1286  			return nil, err
  1287  		}
  1288  		switch errHandling {
  1289  		case ErrHandlingThrowErrorsStr:
  1290  		case ErrHandlingReturnErrorsStr:
  1291  			returnErrors = true
  1292  		default:
  1293  			err = fmt.Errorf("Expected ErrorHandling config value to be '%s' or '%s', was: '%s'", ErrHandlingThrowErrorsStr, ErrHandlingReturnErrorsStr, errHandling)
  1294  			return nil, err
  1295  		}
  1296  
  1297  	}
  1298  	if !returnErrors {
  1299  		fnPrefix = "__"
  1300  	}
  1301  
  1302  	for name, data := range funcs {
  1303  		wfn := makeJSFN(&jsr, name, data)
  1304  		err = jsr.vm.Set(fnPrefix+name, wfn)
  1305  		if err != nil {
  1306  			return nil, err
  1307  		}
  1308  	}
  1309  
  1310  	l := JSLibrary
  1311  	if h != nil {
  1312  		l += fmt.Sprintf(`var App = {Name:"%s",DNA:{Hash:"%s"},Agent:{Hash:"%s",TopHash:"%s",String:"%s"},Key:{Hash:"%s"}};`, h.Name(), h.dnaHash, h.agentHash, h.agentTopHash, jsSanitizeString(string(h.Agent().Identity())), h.nodeIDStr)
  1313  	}
  1314  
  1315  	if !returnErrors {
  1316  		l += `
  1317  		function checkForError(func, rtn) {
  1318  		    if (rtn != null && (typeof rtn === 'object') && rtn.name == "` + HolochainErrorPrefix + `") {
  1319  		        var errsrc = new getErrorSource(4);
  1320  		        throw {
  1321  		            name: "` + HolochainErrorPrefix + `",
  1322  		            function: func,
  1323  		            errorMessage: rtn.message,
  1324  		            source: errsrc,
  1325  		            toString: function () { return JSON.stringify(this); }
  1326  		        }
  1327  		    }
  1328  		    return rtn;
  1329  		}
  1330  
  1331  		function getErrorSource(depth) {
  1332  		    try {
  1333  		        //Throw an error to generate a stack trace
  1334  		        throw new Error();
  1335  		    }
  1336  		    catch (e) {
  1337  		        // get the Xth line of the stack trace
  1338  		        var line = e.stack.split('\n')[depth];
  1339  
  1340  		        // pull out the useful data
  1341  		        var reg = /at (.*) \(.*:(.*):(.*)\)/g.exec(line);
  1342  		        if (reg) {
  1343  		            this.functionName = reg[1];
  1344  		            this.line = reg[2];
  1345  		            this.column = reg[3];
  1346  		        }
  1347  		    }
  1348  		}`
  1349  
  1350  		for name, data := range funcs {
  1351  			var args []Arg
  1352  			args = data.apiFn.Args()
  1353  
  1354  			var argstr string
  1355  			switch len(args) {
  1356  			case 1:
  1357  				argstr = "a"
  1358  			case 2:
  1359  				argstr = "a,b"
  1360  			case 3:
  1361  				argstr = "a,b,c"
  1362  			case 4:
  1363  				argstr = "a,b,c,d"
  1364  			}
  1365  			l += fmt.Sprintf(`function %s(%s){return checkForError("%s",__%s(%s))}`, name, argstr, name, name, argstr)
  1366  		}
  1367  	}
  1368  
  1369  	l += `
  1370  // helper function to determine if value returned from holochain function is an error
  1371  function isErr(result) {
  1372      return (result != null && (typeof result === 'object') && result.name == "` + HolochainErrorPrefix + `");
  1373  }`
  1374  
  1375  	_, err = jsr.Run(l + zome.Code)
  1376  	if err != nil {
  1377  		return
  1378  	}
  1379  	n = &jsr
  1380  	return
  1381  }
  1382  
  1383  func makeJSFN(jsr *JSRibosome, name string, data fnData) func(call otto.FunctionCall) (result otto.Value) {
  1384  	return func(call otto.FunctionCall) (result otto.Value) {
  1385  		var args []Arg
  1386  		args = data.apiFn.Args()
  1387  
  1388  		err := jsProcessArgs(jsr, args, call.ArgumentList)
  1389  		if err == nil {
  1390  			result, err = data.f(args, data.apiFn, call)
  1391  
  1392  		}
  1393  		if err != nil {
  1394  			result = mkOttoErr(jsr, err.Error())
  1395  		}
  1396  		return result
  1397  	}
  1398  }
  1399  
  1400  // Run executes javascript code
  1401  func (jsr *JSRibosome) Run(code string) (result interface{}, err error) {
  1402  	v, err := jsr.vm.Run(code)
  1403  	if err != nil {
  1404  		errStr := err.Error()
  1405  		if !strings.HasPrefix(errStr, "{") {
  1406  			err = fmt.Errorf("Error executing JavaScript: " + errStr)
  1407  		}
  1408  		return
  1409  	}
  1410  	jsr.lastResult = &v
  1411  	result = &v
  1412  	return
  1413  }
  1414  
  1415  func (jsr *JSRibosome) RunAsyncSendResponse(response AppMsg, callback string, callbackID string) (result interface{}, err error) {
  1416  
  1417  	code := fmt.Sprintf(`%s(JSON.parse("%s"),"%s")`, callback, jsSanitizeString(response.Body), jsSanitizeString(callbackID))
  1418  	jsr.h.Debugf("Calling %s\n", code)
  1419  	result, err = jsr.Run(code)
  1420  
  1421  	return
  1422  }