From a7ec50bd51a5f598adc16d7db853d5f82199e32b Mon Sep 17 00:00:00 2001 From: alexiscolin Date: Thu, 11 Dec 2025 13:49:32 +0900 Subject: [PATCH 1/3] fix(gnoweb): update ts URL construction in _fetchQEval to handle existing protocols --- .../pkg/gnoweb/frontend/js/controller-action-function.ts | 7 ++++++- .../pkg/gnoweb/public/js/controller-action-function.js | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts b/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts index e634b5c01ad..1a0ec041d26 100644 --- a/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts +++ b/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts @@ -211,7 +211,12 @@ export class ActionFunctionController extends BaseController { // Fetch the qeval result from the remote private async _fetchQEval(remote: string, data: string): Promise { try { - const url = `http://${remote}/abci_query?path=vm%2fqeval&data=${btoa(data)}`; + // If remote already has a protocol, use it as-is; otherwise prepend http:// + const baseUrl = + remote.startsWith("http://") || remote.startsWith("https://") + ? remote + : `http://${remote}`; + const url = `${baseUrl}/abci_query?path=vm%2fqeval&data=${btoa(data)}`; const response = await fetch(url); if (!response.ok) return ""; diff --git a/gno.land/pkg/gnoweb/public/js/controller-action-function.js b/gno.land/pkg/gnoweb/public/js/controller-action-function.js index 27f7e444dd5..2b8a6fcda0e 100644 --- a/gno.land/pkg/gnoweb/public/js/controller-action-function.js +++ b/gno.land/pkg/gnoweb/public/js/controller-action-function.js @@ -1 +1 @@ -import{BaseController as c,debounce as u,escapeShellSpecialChars as d}from"./controller.js";var l=class extends c{sendValue=null;connect(){this.initializeDOM({"send-code":this.getTargets("send-code")}),this._funcName=this.getValue("name")||null,this._pkgPath=this.getValue("pkgpath")||null,this._initializeArgs(),this._listenForEvents(),this._updateQEvalResult()}_listenForEvents(){this.on("mode:changed",t=>{let e=t.detail.mode;this._updateAllFunctionsMode(e)}),this.on("address:changed",t=>{let e=t.detail.address;this._updateAllFunctionsAddress(e)})}_updateAllFunctionsMode(t){this.getTargets("mode").forEach(e=>{let a=this.getValue("mode",e)===t;e.classList.toggle("u-inline",a),e.classList.toggle("u-hidden",!a),e.dataset.copyTarget=a&&this._funcName?`action-function-${this._funcName}`:""})}_updateAllFunctionsAddress(t){this.getTargets("address").forEach(e=>{e.textContent=t.trim()||"ADDRESS"})}_sanitizeArgsInput(t){let s=this.getValue("param",t)||"",e=t.value.trim();return s||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:s,paramValue:e}}_getParamCurrentValue(t){let s=this.getTargets("param-input").filter(a=>this.getValue("param",a)===t).map(a=>a);if(!s.length)return"";let e=s[0];return e.type==="checkbox"?s.filter(a=>a.checked).map(a=>a.value.trim()).join(","):e.type==="radio"?s.find(n=>n.checked)?.value.trim()||"":e.value.trim()}_initializeArgs(){let t=new Set;this.getTargets("param-input").forEach(s=>{let e=s,a=this.getValue("param",e)||"";if(!a||t.has(a))return;let n=this._getParamCurrentValue(a);n&&this._updateArgInDOM(a,n),t.add(a)})}_debouncedUpdateAllArgs=u((t,s)=>{t&&(this._updateArgInDOM(t,s),this._updateQEvalResult())},50);_updateArgInDOM(t,s){let e=d(s);this.getTargets("arg").filter(n=>this.getValue("arg",n)===t).forEach(n=>{n.textContent=e||""});let a=[...this.getTargets("function-execute"),...this.getTargets("function-anchor")];a.length>0&&a.forEach(n=>{let o=n.hasAttribute("href")?"href":"data-copy-text-value",i=n.getAttribute(o);if(!i){console.warn(`No href or data-copy-text-value attribute found for the function link: ${n}.`);return}let r=new URL(i,window.location.origin);r.searchParams.set(t,s),n.setAttribute(o,r.toString()||"")})}async _updateQEvalResult(){let t=this.getTarget("qeval-result"),s=this.getTarget("remote");if(!(t&&s))return;let e=this.getTargets("arg");if(!e.every(r=>r.textContent!=="")){t.textContent="(enter param values)",t.classList.remove("u-color-danger");return}let n=e.map(r=>r.textContent.replace(/\\(.)/g,"$1")).join(","),o=`${this._pkgPath}.${this._funcName}(${n})`,i=await this._fetchQEval(s.textContent||"",o);t.textContent=i,t.classList.toggle("u-color-danger",i.startsWith("Error:"))}async _fetchQEval(t,s){try{let e=`http://${t}/abci_query?path=vm%2fqeval&data=${btoa(s)}`,a=await fetch(e);if(!a.ok)return"";let n=(await a.json()).result.response.ResponseBase;return n.Data?atob(n.Data):`Error: ${n.Error.value}`}catch{return""}}updateAllArgs(t){let s=t.target,e=this.getValue("param",s);if(!e)return;let a=this._getParamCurrentValue(e);this._debouncedUpdateAllArgs(e,a)}updateAllFunctionsSend(t){let s=t.params?.send||!1;this.getDOMArray("send-code").forEach(e=>{e.textContent=s?this.getValue("send"):""})}};export{l as ActionFunctionController}; +import{BaseController as c,debounce as u,escapeShellSpecialChars as d}from"./controller.js";var l=class extends c{sendValue=null;connect(){this.initializeDOM({"send-code":this.getTargets("send-code")}),this._funcName=this.getValue("name")||null,this._pkgPath=this.getValue("pkgpath")||null,this._initializeArgs(),this._listenForEvents(),this._updateQEvalResult()}_listenForEvents(){this.on("mode:changed",t=>{let e=t.detail.mode;this._updateAllFunctionsMode(e)}),this.on("address:changed",t=>{let e=t.detail.address;this._updateAllFunctionsAddress(e)})}_updateAllFunctionsMode(t){this.getTargets("mode").forEach(e=>{let a=this.getValue("mode",e)===t;e.classList.toggle("u-inline",a),e.classList.toggle("u-hidden",!a),e.dataset.copyTarget=a&&this._funcName?`action-function-${this._funcName}`:""})}_updateAllFunctionsAddress(t){this.getTargets("address").forEach(e=>{e.textContent=t.trim()||"ADDRESS"})}_sanitizeArgsInput(t){let s=this.getValue("param",t)||"",e=t.value.trim();return s||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:s,paramValue:e}}_getParamCurrentValue(t){let s=this.getTargets("param-input").filter(a=>this.getValue("param",a)===t).map(a=>a);if(!s.length)return"";let e=s[0];return e.type==="checkbox"?s.filter(a=>a.checked).map(a=>a.value.trim()).join(","):e.type==="radio"?s.find(n=>n.checked)?.value.trim()||"":e.value.trim()}_initializeArgs(){let t=new Set;this.getTargets("param-input").forEach(s=>{let e=s,a=this.getValue("param",e)||"";if(!a||t.has(a))return;let n=this._getParamCurrentValue(a);n&&this._updateArgInDOM(a,n),t.add(a)})}_debouncedUpdateAllArgs=u((t,s)=>{t&&(this._updateArgInDOM(t,s),this._updateQEvalResult())},50);_updateArgInDOM(t,s){let e=d(s);this.getTargets("arg").filter(n=>this.getValue("arg",n)===t).forEach(n=>{n.textContent=e||""});let a=[...this.getTargets("function-execute"),...this.getTargets("function-anchor")];a.length>0&&a.forEach(n=>{let r=n.hasAttribute("href")?"href":"data-copy-text-value",o=n.getAttribute(r);if(!o){console.warn(`No href or data-copy-text-value attribute found for the function link: ${n}.`);return}let i=new URL(o,window.location.origin);i.searchParams.set(t,s),n.setAttribute(r,i.toString()||"")})}async _updateQEvalResult(){let t=this.getTarget("qeval-result"),s=this.getTarget("remote");if(!(t&&s))return;let e=this.getTargets("arg");if(!e.every(i=>i.textContent!=="")){t.textContent="(enter param values)",t.classList.remove("u-color-danger");return}let n=e.map(i=>i.textContent.replace(/\\(.)/g,"$1")).join(","),r=`${this._pkgPath}.${this._funcName}(${n})`,o=await this._fetchQEval(s.textContent||"",r);t.textContent=o,t.classList.toggle("u-color-danger",o.startsWith("Error:"))}async _fetchQEval(t,s){try{let a=`${t.startsWith("http://")||t.startsWith("https://")?t:`http://${t}`}/abci_query?path=vm%2fqeval&data=${btoa(s)}`,n=await fetch(a);if(!n.ok)return"";let r=(await n.json()).result.response.ResponseBase;return r.Data?atob(r.Data):`Error: ${r.Error.value}`}catch{return""}}updateAllArgs(t){let s=t.target,e=this.getValue("param",s);if(!e)return;let a=this._getParamCurrentValue(e);this._debouncedUpdateAllArgs(e,a)}updateAllFunctionsSend(t){let s=t.params?.send||!1;this.getDOMArray("send-code").forEach(e=>{e.textContent=s?this.getValue("send"):""})}};export{l as ActionFunctionController}; From 76dabeec19294625c7deb7267b271185fbe3f09c Mon Sep 17 00:00:00 2001 From: alexiscolin Date: Thu, 11 Dec 2025 18:35:59 +0900 Subject: [PATCH 2/3] feat: add tcp --- .../frontend/js/controller-action-function.ts | 16 +++++++++++----- .../public/js/controller-action-function.js | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts b/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts index 1a0ec041d26..ca077dd3da0 100644 --- a/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts +++ b/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts @@ -208,14 +208,20 @@ export class ActionFunctionController extends BaseController { ); } + // Normalize a remote URL for browser fetch + // - http:// and https:// are used as-is + // - tcp:// is converted to http:// (RPC over HTTP) + // - No protocol defaults to http:// + private _normalizeRemoteUrl(url: string): string { + if (url.startsWith("tcp://")) return url.replace("tcp://", "http://"); + if (url.startsWith("http://") || url.startsWith("https://")) return url; + return `http://${url}`; + } + // Fetch the qeval result from the remote private async _fetchQEval(remote: string, data: string): Promise { try { - // If remote already has a protocol, use it as-is; otherwise prepend http:// - const baseUrl = - remote.startsWith("http://") || remote.startsWith("https://") - ? remote - : `http://${remote}`; + const baseUrl = this._normalizeRemoteUrl(remote); const url = `${baseUrl}/abci_query?path=vm%2fqeval&data=${btoa(data)}`; const response = await fetch(url); if (!response.ok) return ""; diff --git a/gno.land/pkg/gnoweb/public/js/controller-action-function.js b/gno.land/pkg/gnoweb/public/js/controller-action-function.js index 2b8a6fcda0e..c28894bcfad 100644 --- a/gno.land/pkg/gnoweb/public/js/controller-action-function.js +++ b/gno.land/pkg/gnoweb/public/js/controller-action-function.js @@ -1 +1 @@ -import{BaseController as c,debounce as u,escapeShellSpecialChars as d}from"./controller.js";var l=class extends c{sendValue=null;connect(){this.initializeDOM({"send-code":this.getTargets("send-code")}),this._funcName=this.getValue("name")||null,this._pkgPath=this.getValue("pkgpath")||null,this._initializeArgs(),this._listenForEvents(),this._updateQEvalResult()}_listenForEvents(){this.on("mode:changed",t=>{let e=t.detail.mode;this._updateAllFunctionsMode(e)}),this.on("address:changed",t=>{let e=t.detail.address;this._updateAllFunctionsAddress(e)})}_updateAllFunctionsMode(t){this.getTargets("mode").forEach(e=>{let a=this.getValue("mode",e)===t;e.classList.toggle("u-inline",a),e.classList.toggle("u-hidden",!a),e.dataset.copyTarget=a&&this._funcName?`action-function-${this._funcName}`:""})}_updateAllFunctionsAddress(t){this.getTargets("address").forEach(e=>{e.textContent=t.trim()||"ADDRESS"})}_sanitizeArgsInput(t){let s=this.getValue("param",t)||"",e=t.value.trim();return s||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:s,paramValue:e}}_getParamCurrentValue(t){let s=this.getTargets("param-input").filter(a=>this.getValue("param",a)===t).map(a=>a);if(!s.length)return"";let e=s[0];return e.type==="checkbox"?s.filter(a=>a.checked).map(a=>a.value.trim()).join(","):e.type==="radio"?s.find(n=>n.checked)?.value.trim()||"":e.value.trim()}_initializeArgs(){let t=new Set;this.getTargets("param-input").forEach(s=>{let e=s,a=this.getValue("param",e)||"";if(!a||t.has(a))return;let n=this._getParamCurrentValue(a);n&&this._updateArgInDOM(a,n),t.add(a)})}_debouncedUpdateAllArgs=u((t,s)=>{t&&(this._updateArgInDOM(t,s),this._updateQEvalResult())},50);_updateArgInDOM(t,s){let e=d(s);this.getTargets("arg").filter(n=>this.getValue("arg",n)===t).forEach(n=>{n.textContent=e||""});let a=[...this.getTargets("function-execute"),...this.getTargets("function-anchor")];a.length>0&&a.forEach(n=>{let r=n.hasAttribute("href")?"href":"data-copy-text-value",o=n.getAttribute(r);if(!o){console.warn(`No href or data-copy-text-value attribute found for the function link: ${n}.`);return}let i=new URL(o,window.location.origin);i.searchParams.set(t,s),n.setAttribute(r,i.toString()||"")})}async _updateQEvalResult(){let t=this.getTarget("qeval-result"),s=this.getTarget("remote");if(!(t&&s))return;let e=this.getTargets("arg");if(!e.every(i=>i.textContent!=="")){t.textContent="(enter param values)",t.classList.remove("u-color-danger");return}let n=e.map(i=>i.textContent.replace(/\\(.)/g,"$1")).join(","),r=`${this._pkgPath}.${this._funcName}(${n})`,o=await this._fetchQEval(s.textContent||"",r);t.textContent=o,t.classList.toggle("u-color-danger",o.startsWith("Error:"))}async _fetchQEval(t,s){try{let a=`${t.startsWith("http://")||t.startsWith("https://")?t:`http://${t}`}/abci_query?path=vm%2fqeval&data=${btoa(s)}`,n=await fetch(a);if(!n.ok)return"";let r=(await n.json()).result.response.ResponseBase;return r.Data?atob(r.Data):`Error: ${r.Error.value}`}catch{return""}}updateAllArgs(t){let s=t.target,e=this.getValue("param",s);if(!e)return;let a=this._getParamCurrentValue(e);this._debouncedUpdateAllArgs(e,a)}updateAllFunctionsSend(t){let s=t.params?.send||!1;this.getDOMArray("send-code").forEach(e=>{e.textContent=s?this.getValue("send"):""})}};export{l as ActionFunctionController}; +import{BaseController as c,debounce as u,escapeShellSpecialChars as g}from"./controller.js";var l=class extends c{sendValue=null;connect(){this.initializeDOM({"send-code":this.getTargets("send-code")}),this._funcName=this.getValue("name")||null,this._pkgPath=this.getValue("pkgpath")||null,this._initializeArgs(),this._listenForEvents(),this._updateQEvalResult()}_listenForEvents(){this.on("mode:changed",t=>{let e=t.detail.mode;this._updateAllFunctionsMode(e)}),this.on("address:changed",t=>{let e=t.detail.address;this._updateAllFunctionsAddress(e)})}_updateAllFunctionsMode(t){this.getTargets("mode").forEach(e=>{let a=this.getValue("mode",e)===t;e.classList.toggle("u-inline",a),e.classList.toggle("u-hidden",!a),e.dataset.copyTarget=a&&this._funcName?`action-function-${this._funcName}`:""})}_updateAllFunctionsAddress(t){this.getTargets("address").forEach(e=>{e.textContent=t.trim()||"ADDRESS"})}_sanitizeArgsInput(t){let s=this.getValue("param",t)||"",e=t.value.trim();return s||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:s,paramValue:e}}_getParamCurrentValue(t){let s=this.getTargets("param-input").filter(a=>this.getValue("param",a)===t).map(a=>a);if(!s.length)return"";let e=s[0];return e.type==="checkbox"?s.filter(a=>a.checked).map(a=>a.value.trim()).join(","):e.type==="radio"?s.find(r=>r.checked)?.value.trim()||"":e.value.trim()}_initializeArgs(){let t=new Set;this.getTargets("param-input").forEach(s=>{let e=s,a=this.getValue("param",e)||"";if(!a||t.has(a))return;let r=this._getParamCurrentValue(a);r&&this._updateArgInDOM(a,r),t.add(a)})}_debouncedUpdateAllArgs=u((t,s)=>{t&&(this._updateArgInDOM(t,s),this._updateQEvalResult())},50);_updateArgInDOM(t,s){let e=g(s);this.getTargets("arg").filter(r=>this.getValue("arg",r)===t).forEach(r=>{r.textContent=e||""});let a=[...this.getTargets("function-execute"),...this.getTargets("function-anchor")];a.length>0&&a.forEach(r=>{let n=r.hasAttribute("href")?"href":"data-copy-text-value",o=r.getAttribute(n);if(!o){console.warn(`No href or data-copy-text-value attribute found for the function link: ${r}.`);return}let i=new URL(o,window.location.origin);i.searchParams.set(t,s),r.setAttribute(n,i.toString()||"")})}async _updateQEvalResult(){let t=this.getTarget("qeval-result"),s=this.getTarget("remote");if(!(t&&s))return;let e=this.getTargets("arg");if(!e.every(i=>i.textContent!=="")){t.textContent="(enter param values)",t.classList.remove("u-color-danger");return}let r=e.map(i=>i.textContent.replace(/\\(.)/g,"$1")).join(","),n=`${this._pkgPath}.${this._funcName}(${r})`,o=await this._fetchQEval(s.textContent||"",n);t.textContent=o,t.classList.toggle("u-color-danger",o.startsWith("Error:"))}_normalizeRemoteUrl(t){return t.startsWith("tcp://")?t.replace("tcp://","http://"):t.startsWith("http://")||t.startsWith("https://")?t:`http://${t}`}async _fetchQEval(t,s){try{let a=`${this._normalizeRemoteUrl(t)}/abci_query?path=vm%2fqeval&data=${btoa(s)}`,r=await fetch(a);if(!r.ok)return"";let n=(await r.json()).result.response.ResponseBase;return n.Data?atob(n.Data):`Error: ${n.Error.value}`}catch{return""}}updateAllArgs(t){let s=t.target,e=this.getValue("param",s);if(!e)return;let a=this._getParamCurrentValue(e);this._debouncedUpdateAllArgs(e,a)}updateAllFunctionsSend(t){let s=t.params?.send||!1;this.getDOMArray("send-code").forEach(e=>{e.textContent=s?this.getValue("send"):""})}};export{l as ActionFunctionController}; From 65a3518a41b97a8df270a4d73cfb8e3486aa4cac Mon Sep 17 00:00:00 2001 From: alexiscolin Date: Fri, 12 Dec 2025 15:12:29 +0900 Subject: [PATCH 3/3] feat: implement normalizeRemoteURL function and add tests --- gno.land/cmd/gnoweb/main.go | 22 +++++++++++++---- gno.land/cmd/gnoweb/main_test.go | 24 +++++++++++++++++++ .../frontend/js/controller-action-function.ts | 13 +--------- .../public/js/controller-action-function.js | 2 +- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 94876ada149..1db577587cf 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -18,6 +18,20 @@ import ( "go.uber.org/zap/zapcore" ) +// normalizeRemoteURL ensures the remote URL has a valid HTTP(S) protocol. +// - tcp:// is converted to http:// (RPC uses HTTP over TCP) +// - No protocol defaults to http:// +// - http:// and https:// are kept as-is +func normalizeRemoteURL(remote string) string { + if strings.HasPrefix(remote, "tcp://") { + return strings.Replace(remote, "tcp://", "http://", 1) + } + if strings.HasPrefix(remote, "http://") || strings.HasPrefix(remote, "https://") { + return remote + } + return "http://" + remote +} + // Authorized external image host providers. var cspImgHost = []string{ // Gno-related hosts @@ -225,10 +239,10 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { // Setup app appcfg := gnoweb.NewDefaultAppConfig() appcfg.ChainID = cfg.chainid - appcfg.NodeRemote = cfg.remote + appcfg.NodeRemote = normalizeRemoteURL(cfg.remote) appcfg.NodeRequestTimeout = cfg.remoteTimeout - appcfg.RemoteHelp = cfg.remoteHelp - if appcfg.RemoteHelp == "" { + appcfg.RemoteHelp = normalizeRemoteURL(cfg.remoteHelp) + if appcfg.RemoteHelp == "" || appcfg.RemoteHelp == "http://" { appcfg.RemoteHelp = appcfg.NodeRemote } appcfg.Analytics = cfg.analytics @@ -262,7 +276,7 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger.Info("Running", "listener", bindaddr.String()) // Setup security headers - secureHandler := SecureHeadersMiddleware(app, !cfg.noStrict, cfg.remote) + secureHandler := SecureHeadersMiddleware(app, !cfg.noStrict, appcfg.NodeRemote) // Setup server server := &http.Server{ diff --git a/gno.land/cmd/gnoweb/main_test.go b/gno.land/cmd/gnoweb/main_test.go index 04bbf23e566..dfce52349fa 100644 --- a/gno.land/cmd/gnoweb/main_test.go +++ b/gno.land/cmd/gnoweb/main_test.go @@ -13,6 +13,30 @@ import ( "github.com/stretchr/testify/require" ) +func TestNormalizeRemoteURL(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input string + expected string + }{ + {"tcp protocol", "tcp://127.0.0.1:26657", "http://127.0.0.1:26657"}, + {"http protocol", "http://127.0.0.1:26657", "http://127.0.0.1:26657"}, + {"https protocol", "https://rpc.gno.land:443", "https://rpc.gno.land:443"}, + {"no protocol", "127.0.0.1:26657", "http://127.0.0.1:26657"}, + {"no protocol with domain", "rpc.gno.land:443", "http://rpc.gno.land:443"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + result := normalizeRemoteURL(tc.input) + assert.Equal(t, tc.expected, result) + }) + } +} + func TestSetupWeb(t *testing.T) { opts := defaultWebOptions opts.bind = "127.0.0.1:0" // random port diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts b/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts index ca077dd3da0..797e83057a8 100644 --- a/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts +++ b/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts @@ -208,21 +208,10 @@ export class ActionFunctionController extends BaseController { ); } - // Normalize a remote URL for browser fetch - // - http:// and https:// are used as-is - // - tcp:// is converted to http:// (RPC over HTTP) - // - No protocol defaults to http:// - private _normalizeRemoteUrl(url: string): string { - if (url.startsWith("tcp://")) return url.replace("tcp://", "http://"); - if (url.startsWith("http://") || url.startsWith("https://")) return url; - return `http://${url}`; - } - // Fetch the qeval result from the remote private async _fetchQEval(remote: string, data: string): Promise { try { - const baseUrl = this._normalizeRemoteUrl(remote); - const url = `${baseUrl}/abci_query?path=vm%2fqeval&data=${btoa(data)}`; + const url = `${remote}/abci_query?path=vm%2fqeval&data=${btoa(data)}`; const response = await fetch(url); if (!response.ok) return ""; diff --git a/gno.land/pkg/gnoweb/public/js/controller-action-function.js b/gno.land/pkg/gnoweb/public/js/controller-action-function.js index c28894bcfad..90924c3f88f 100644 --- a/gno.land/pkg/gnoweb/public/js/controller-action-function.js +++ b/gno.land/pkg/gnoweb/public/js/controller-action-function.js @@ -1 +1 @@ -import{BaseController as c,debounce as u,escapeShellSpecialChars as g}from"./controller.js";var l=class extends c{sendValue=null;connect(){this.initializeDOM({"send-code":this.getTargets("send-code")}),this._funcName=this.getValue("name")||null,this._pkgPath=this.getValue("pkgpath")||null,this._initializeArgs(),this._listenForEvents(),this._updateQEvalResult()}_listenForEvents(){this.on("mode:changed",t=>{let e=t.detail.mode;this._updateAllFunctionsMode(e)}),this.on("address:changed",t=>{let e=t.detail.address;this._updateAllFunctionsAddress(e)})}_updateAllFunctionsMode(t){this.getTargets("mode").forEach(e=>{let a=this.getValue("mode",e)===t;e.classList.toggle("u-inline",a),e.classList.toggle("u-hidden",!a),e.dataset.copyTarget=a&&this._funcName?`action-function-${this._funcName}`:""})}_updateAllFunctionsAddress(t){this.getTargets("address").forEach(e=>{e.textContent=t.trim()||"ADDRESS"})}_sanitizeArgsInput(t){let s=this.getValue("param",t)||"",e=t.value.trim();return s||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:s,paramValue:e}}_getParamCurrentValue(t){let s=this.getTargets("param-input").filter(a=>this.getValue("param",a)===t).map(a=>a);if(!s.length)return"";let e=s[0];return e.type==="checkbox"?s.filter(a=>a.checked).map(a=>a.value.trim()).join(","):e.type==="radio"?s.find(r=>r.checked)?.value.trim()||"":e.value.trim()}_initializeArgs(){let t=new Set;this.getTargets("param-input").forEach(s=>{let e=s,a=this.getValue("param",e)||"";if(!a||t.has(a))return;let r=this._getParamCurrentValue(a);r&&this._updateArgInDOM(a,r),t.add(a)})}_debouncedUpdateAllArgs=u((t,s)=>{t&&(this._updateArgInDOM(t,s),this._updateQEvalResult())},50);_updateArgInDOM(t,s){let e=g(s);this.getTargets("arg").filter(r=>this.getValue("arg",r)===t).forEach(r=>{r.textContent=e||""});let a=[...this.getTargets("function-execute"),...this.getTargets("function-anchor")];a.length>0&&a.forEach(r=>{let n=r.hasAttribute("href")?"href":"data-copy-text-value",o=r.getAttribute(n);if(!o){console.warn(`No href or data-copy-text-value attribute found for the function link: ${r}.`);return}let i=new URL(o,window.location.origin);i.searchParams.set(t,s),r.setAttribute(n,i.toString()||"")})}async _updateQEvalResult(){let t=this.getTarget("qeval-result"),s=this.getTarget("remote");if(!(t&&s))return;let e=this.getTargets("arg");if(!e.every(i=>i.textContent!=="")){t.textContent="(enter param values)",t.classList.remove("u-color-danger");return}let r=e.map(i=>i.textContent.replace(/\\(.)/g,"$1")).join(","),n=`${this._pkgPath}.${this._funcName}(${r})`,o=await this._fetchQEval(s.textContent||"",n);t.textContent=o,t.classList.toggle("u-color-danger",o.startsWith("Error:"))}_normalizeRemoteUrl(t){return t.startsWith("tcp://")?t.replace("tcp://","http://"):t.startsWith("http://")||t.startsWith("https://")?t:`http://${t}`}async _fetchQEval(t,s){try{let a=`${this._normalizeRemoteUrl(t)}/abci_query?path=vm%2fqeval&data=${btoa(s)}`,r=await fetch(a);if(!r.ok)return"";let n=(await r.json()).result.response.ResponseBase;return n.Data?atob(n.Data):`Error: ${n.Error.value}`}catch{return""}}updateAllArgs(t){let s=t.target,e=this.getValue("param",s);if(!e)return;let a=this._getParamCurrentValue(e);this._debouncedUpdateAllArgs(e,a)}updateAllFunctionsSend(t){let s=t.params?.send||!1;this.getDOMArray("send-code").forEach(e=>{e.textContent=s?this.getValue("send"):""})}};export{l as ActionFunctionController}; +import{BaseController as c,debounce as u,escapeShellSpecialChars as d}from"./controller.js";var l=class extends c{sendValue=null;connect(){this.initializeDOM({"send-code":this.getTargets("send-code")}),this._funcName=this.getValue("name")||null,this._pkgPath=this.getValue("pkgpath")||null,this._initializeArgs(),this._listenForEvents(),this._updateQEvalResult()}_listenForEvents(){this.on("mode:changed",t=>{let e=t.detail.mode;this._updateAllFunctionsMode(e)}),this.on("address:changed",t=>{let e=t.detail.address;this._updateAllFunctionsAddress(e)})}_updateAllFunctionsMode(t){this.getTargets("mode").forEach(e=>{let a=this.getValue("mode",e)===t;e.classList.toggle("u-inline",a),e.classList.toggle("u-hidden",!a),e.dataset.copyTarget=a&&this._funcName?`action-function-${this._funcName}`:""})}_updateAllFunctionsAddress(t){this.getTargets("address").forEach(e=>{e.textContent=t.trim()||"ADDRESS"})}_sanitizeArgsInput(t){let s=this.getValue("param",t)||"",e=t.value.trim();return s||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:s,paramValue:e}}_getParamCurrentValue(t){let s=this.getTargets("param-input").filter(a=>this.getValue("param",a)===t).map(a=>a);if(!s.length)return"";let e=s[0];return e.type==="checkbox"?s.filter(a=>a.checked).map(a=>a.value.trim()).join(","):e.type==="radio"?s.find(n=>n.checked)?.value.trim()||"":e.value.trim()}_initializeArgs(){let t=new Set;this.getTargets("param-input").forEach(s=>{let e=s,a=this.getValue("param",e)||"";if(!a||t.has(a))return;let n=this._getParamCurrentValue(a);n&&this._updateArgInDOM(a,n),t.add(a)})}_debouncedUpdateAllArgs=u((t,s)=>{t&&(this._updateArgInDOM(t,s),this._updateQEvalResult())},50);_updateArgInDOM(t,s){let e=d(s);this.getTargets("arg").filter(n=>this.getValue("arg",n)===t).forEach(n=>{n.textContent=e||""});let a=[...this.getTargets("function-execute"),...this.getTargets("function-anchor")];a.length>0&&a.forEach(n=>{let o=n.hasAttribute("href")?"href":"data-copy-text-value",i=n.getAttribute(o);if(!i){console.warn(`No href or data-copy-text-value attribute found for the function link: ${n}.`);return}let r=new URL(i,window.location.origin);r.searchParams.set(t,s),n.setAttribute(o,r.toString()||"")})}async _updateQEvalResult(){let t=this.getTarget("qeval-result"),s=this.getTarget("remote");if(!(t&&s))return;let e=this.getTargets("arg");if(!e.every(r=>r.textContent!=="")){t.textContent="(enter param values)",t.classList.remove("u-color-danger");return}let n=e.map(r=>r.textContent.replace(/\\(.)/g,"$1")).join(","),o=`${this._pkgPath}.${this._funcName}(${n})`,i=await this._fetchQEval(s.textContent||"",o);t.textContent=i,t.classList.toggle("u-color-danger",i.startsWith("Error:"))}async _fetchQEval(t,s){try{let e=`${t}/abci_query?path=vm%2fqeval&data=${btoa(s)}`,a=await fetch(e);if(!a.ok)return"";let n=(await a.json()).result.response.ResponseBase;return n.Data?atob(n.Data):`Error: ${n.Error.value}`}catch{return""}}updateAllArgs(t){let s=t.target,e=this.getValue("param",s);if(!e)return;let a=this._getParamCurrentValue(e);this._debouncedUpdateAllArgs(e,a)}updateAllFunctionsSend(t){let s=t.params?.send||!1;this.getDOMArray("send-code").forEach(e=>{e.textContent=s?this.getValue("send"):""})}};export{l as ActionFunctionController};