Official plugins
Fetch
A fetch that makes HTTP requests from Go, with no CORS, any header, streamed bodies and cancellation.
The fetch plugin gives pages a fetch that makes HTTP requests from Go. It
takes the options of the standard fetch and returns a standard
Response, but the request leaves from the Go side of the app, so:
- it is not subject to CORS: any server answers, and every response header
is readable but
Set-Cookie, which aResponsecannot hold; - it may set any header, including those the webview forbids, such as
Origin,Cookie,RefererandUser-Agent; - its body streams to the page as Go reads it, and aborting it cancels the request in Go.
Set up
Use the plugin in Go:
import (
"github.com/egoist/mygo"
"github.com/egoist/mygo/plugins/fetch"
)
func main() {
mygo.Use(fetch.Plugin)
// ...
}and add its package to the frontend:
bun add @mygo-plugins/fetchCalling its fetch in an app that does not use the Go half rejects with an
error that says to call mygo.Use.
Making requests
Import fetch from the package and call it as you would the webview's:
import { fetch } from "@mygo-plugins/fetch";
const res = await fetch("https://api.example.com/items", {
method: "POST",
headers: {
authorization: `Bearer ${token}`,
"content-type": "application/json",
},
body: JSON.stringify({ name: "Ada" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const item = await res.json();A Request works in place of the URL. Bodies are those of the standard
fetch, such as strings, FormData, URLSearchParams, Blobs and
ArrayBuffers, with the Content-Type it would give them; they are read
whole before the request starts, streams too.
Libraries that take a fetch of their own, as many API clients do, can be
given this one. Do not assign it to globalThis.fetch: the plugin hands
the URLs it does not make, those whose scheme is not http or https, to the
webview's fetch.
Streaming responses
The body of the response is a ReadableStream that Go fills as the server
sends it, which suits server-sent events and the streamed answers of model
APIs:
const res = await fetch("https://api.example.com/events");
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
output.append(value);
}Go reads no faster than the page: it waits while the page has more than a MiB of the body yet to read, so a large download does not pile up in the page's memory.
Canceling
Aborting the request's signal cancels it in Go, before or after the
response arrived:
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 10_000);
try {
const res = await fetch(url, { signal: ctrl.signal });
return await res.text();
} finally {
clearTimeout(timer);
}The promise, or the reading of the body, rejects with the signal's reason.
Canceling the body (res.body.cancel()), navigating away and closing the
window cancel the request too.
Errors
As with the webview's fetch, a response is not an error, whatever its
status: check res.ok. The promise rejects with a TypeError when the
request fails, such as when the server cannot be reached, TLS fails or
there are too many redirects, and its message says why:
try {
await fetch("https://unreachable.example");
} catch (err) {
console.log(err.message); // fetch failed: Get "https://unreachable.example": dial tcp: lookup unreachable.example: no such host
}Differences from the webview's fetch
mode,credentials,cache,referrerPolicy,integrity,keepaliveandprioritydo not apply and are ignored.- Cookies are those of the Go side's
http.Client, never the webview's: none, unless the client has aJarto keep them (see options). redirect: "manual"returns the redirect response itself, with its status andLocation, instead of an opaque response."error"rejects on a redirect, and"follow", the default, follows up to 20 redirects and setsres.redirectedandres.url.- URLs whose scheme is not http or https, such as the app's own pages in
builds (
mygo://localhost),data:andblob:URLs, go to the webview'sfetch. Relative URLs resolve against the page's: duringmygo devthey are those of the dev server, which Go fetches.
Options
fetch.Plugin makes requests with http.DefaultClient and lets pages
request any URL. fetch.New takes options instead:
jar, _ := cookiejar.New(nil)
mygo.Use(fetch.New(fetch.Options{
Client: &http.Client{Jar: jar, Timeout: time.Minute},
Allow: func(r *http.Request) bool {
return r.URL.Hostname() == "api.example.com"
},
}))Clientmakes the requests, so itsTransport(proxies, TLS settings),Jar(cookies kept between requests) andTimeoutapply, and itsCheckRedirectlimits the redirects followed.http.DefaultClienttakes its proxy from the environment (HTTPS_PROXY,NO_PROXY), not from the system's settings.Allowsees each request before Go makes it, with its URL, method and headers, and decides whether pages may make it: a request it refuses rejects with an error. It sees the first request only, not the redirects that follow, which the client'sCheckRedirectcan refuse.
Only the app's own pages may call the plugin, as with every bound method
(see who may call); Allow keeps a page
that runs code it should not, such as through a cross-site scripting bug,
from reaching other servers.