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 }