diff --git a/go.mod b/go.mod index 184eee2c..52cf3bba 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/aperturerobotics/entitygraph v0.9.1 // latest github.com/aperturerobotics/protobuf-go-lite v0.6.5 // latest github.com/aperturerobotics/starpc v0.33.6 // latest - github.com/aperturerobotics/util v1.24.0 // master + github.com/aperturerobotics/util v1.25.0 // master ) // aperture: use compatibility forks diff --git a/go.sum b/go.sum index d9dfe6eb..341f08c8 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/aperturerobotics/quic-go v0.45.1-0.20240701204210-82dc570e7aa0 h1:KH1 github.com/aperturerobotics/quic-go v0.45.1-0.20240701204210-82dc570e7aa0/go.mod h1:X095EBMI8M7riYQRvUgegHFkEkgM2QKLvyGHyAcOw/Q= github.com/aperturerobotics/starpc v0.33.6 h1:noc/MnmIMTek9bdEvd88QiD1p9KzEV8CUOBIoKmGgm0= github.com/aperturerobotics/starpc v0.33.6/go.mod h1:4IYcbulEzqhPT5jKaDeL1BJPFd8WVWZ7Ugu0/348/Is= -github.com/aperturerobotics/util v1.24.0 h1:QIZ2Fr9H+pV9uYs7JHMQPL4+Lz/2BagdrQq09Kru3mQ= -github.com/aperturerobotics/util v1.24.0/go.mod h1:QiSWcOha1HhCI4f48w6rd3gia9jIMGpfoeJiZMU+jLM= +github.com/aperturerobotics/util v1.25.0 h1:+dfi8QMsy8xzE8Xu7x3PuWSEKkrbfGZ1UHy1YffoXOw= +github.com/aperturerobotics/util v1.25.0/go.mod h1:QiSWcOha1HhCI4f48w6rd3gia9jIMGpfoeJiZMU+jLM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= diff --git a/http/log/fetch/fetch_js.go b/http/log/fetch/fetch_js.go index 56ec6385..f2496bdc 100644 --- a/http/log/fetch/fetch_js.go +++ b/http/log/fetch/fetch_js.go @@ -7,7 +7,7 @@ import ( "strings" "time" - fetch "github.com/aperturerobotics/bifrost/util/js-fetch" + fetch "github.com/aperturerobotics/util/js/fetch" "github.com/sirupsen/logrus" ) diff --git a/http/log/fetch/fetch_test.go b/http/log/fetch/fetch_test.go index 454a1567..81334111 100644 --- a/http/log/fetch/fetch_test.go +++ b/http/log/fetch/fetch_test.go @@ -7,7 +7,7 @@ import ( "net/http" "testing" - fetch "github.com/aperturerobotics/bifrost/util/js-fetch" + fetch "github.com/aperturerobotics/util/js/fetch" "github.com/sirupsen/logrus" ) diff --git a/util/js-fetch/LICENSE b/util/js-fetch/LICENSE deleted file mode 100644 index eaccbda9..00000000 --- a/util/js-fetch/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -This sub-directory is adapted from wasm-fetch (MIT License). - -Upstream: https://github.com/marwan-at-work/wasm-fetch - -MIT License - -Copyright (c) 2024 Christian Stewart -Copyright (c) 2020 Marwan Sulaiman - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/util/js-fetch/README.md b/util/js-fetch/README.md deleted file mode 100644 index 08e8288a..00000000 --- a/util/js-fetch/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Fetch - -Wrapper for the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) - -## Upstream - -Upstream: https://github.com/marwan-at-work/wasm-fetch - -Forked here. This sub-directory (only) is licensed MIT. diff --git a/util/js-fetch/doc.go b/util/js-fetch/doc.go deleted file mode 100644 index 7197786a..00000000 --- a/util/js-fetch/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -// Package fetch is a js fetch wrapper that avoids importing net/http. -/* - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - resp, err := fetch.Fetch("/some/api/call", &fetch.Opts{ - Body: strings.NewReader(`{"one": "two"}`), - Method: fetch.MethodPost, - Signal: ctx, - }) -*/ -package fetch diff --git a/util/js-fetch/enums.go b/util/js-fetch/enums.go deleted file mode 100644 index 63fae9e7..00000000 --- a/util/js-fetch/enums.go +++ /dev/null @@ -1,65 +0,0 @@ -//go:build js - -package fetch - -// cache enums -const ( - CacheDefault = "default" - CacheNoStore = "no-store" - CacheReload = "reload" - CacheNone = "no-cache" - CacheForce = "force-cache" - CacheOnlyIfCached = "only-if-cached" -) - -// credentials enums -const ( - CredentialsOmit = "omit" - CredentialsSameOrigin = "same-origin" - CredentialsInclude = "include" -) - -// Common HTTP methods. -// -// Unless otherwise noted, these are defined in RFC 7231 section 4.3. -const ( - MethodGet = "GET" - MethodHead = "HEAD" - MethodPost = "POST" - MethodPut = "PUT" - MethodPatch = "PATCH" // RFC 5789 - MethodDelete = "DELETE" - MethodConnect = "CONNECT" - MethodOptions = "OPTIONS" - MethodTrace = "TRACE" -) - -// Mode enums -const ( - ModeSameOrigin = "same-origin" - ModeNoCORS = "no-cors" - ModeCORS = "cors" - ModeNavigate = "navigate" -) - -// Redirect enums -const ( - RedirectFollow = "follow" - RedirectError = "error" - RedirectManual = "manual" -) - -// Referrer enums -const ( - ReferrerNone = "no-referrer" - ReferrerClient = "client" -) - -// ReferrerPolicy enums -const ( - ReferrerPolicyNone = "no-referrer" - ReferrerPolicyNoDowngrade = "no-referrer-when-downgrade" - ReferrerPolicyOrigin = "origin" - ReferrerPolicyCrossOrigin = "origin-when-cross-origin" - ReferrerPolicyUnsafeURL = "unsafe-url" -) diff --git a/util/js-fetch/fetch.go b/util/js-fetch/fetch.go deleted file mode 100644 index d08d0e93..00000000 --- a/util/js-fetch/fetch.go +++ /dev/null @@ -1,275 +0,0 @@ -//go:build js - -package fetch - -import ( - "context" - "errors" - "io" - "syscall/js" -) - -// Opts are the options for Fetch. -type Opts struct { - // CommonOpts are the common Fetch options. - CommonOpts - - // Method specifies the HTTP method (GET, POST, PUT, etc.). - // For client requests, an empty string means GET. - // constants are copied from net/http to avoid import - Method string - - // Header contains the request header fields. - // - // Example: - // - // Host: example.com - // accept-encoding: gzip, deflate - // Accept-Language: en-us - // fOO: Bar - // foo: two - // - // then - // - // Header = map[string][]string{ - // "Accept-Encoding": {"gzip, deflate"}, - // "Accept-Language": {"en-us"}, - // "Foo": {"Bar", "two"}, - // } - // - // HTTP defines that header names are case-insensitive. The - // request parser implements this by using CanonicalHeaderKey, - // making the first character and any characters following a - // hyphen uppercase and the rest lowercase. - // - // Certain headers such as Content-Length and Connection are automatically - // written when needed and values in Header may be ignored. - Header Header - - // Body is the request's body. - // - // For client requests, a nil body means the request has no - // body, such as a GET request. The HTTP Client's Transport - // is responsible for calling the Close method. - // - // Body must allow Read to be called concurrently with Close. - // In particular, calling Close should unblock a Read waiting - // for input. - Body io.Reader - - // Signal docs https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal - Signal context.Context -} - -// CommonOpts are opts for Fetch that can be reused between requests. -type CommonOpts struct { - // Mode docs https://developer.mozilla.org/en-US/docs/Web/API/Request/mode - Mode string - - // Credentials docs https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials - Credentials string - - // Cache docs https://developer.mozilla.org/en-US/docs/Web/API/Request/cache - Cache string - - // Redirect docs https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch - Redirect string - - // Referrer docs https://developer.mozilla.org/en-US/docs/Web/API/Request/referrer - Referrer string - - // ReferrerPolicy docs https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch - ReferrerPolicy string - - // Integrity docs https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity - Integrity string - - // KeepAlive docs https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch - KeepAlive *bool -} - -// Clone clones the opts, excluding the Body field. -func (o *Opts) Clone() *Opts { - if o == nil { - return nil - } - - clone := &Opts{ - CommonOpts: o.CommonOpts, - Method: o.Method, - Header: o.Header.Clone(), - Signal: o.Signal, - } - - if o.CommonOpts.KeepAlive != nil { - keepAliveValue := *o.CommonOpts.KeepAlive - clone.CommonOpts.KeepAlive = &keepAliveValue - } - - return clone -} - -// Response is the response that returns from the fetch promise. -type Response struct { - Headers Header - OK bool - Redirected bool - Status int - StatusText string - Type string - URL string - Body []byte - BodyUsed bool -} - -// Fetch uses the JS Fetch API to make requests. -func Fetch(url string, opts *Opts) (*Response, error) { - optsMap, err := mapOpts(opts) - if err != nil { - return nil, err - } - - type fetchResponse struct { - r *Response - b js.Value - e error - } - ch := make(chan *fetchResponse) - if opts != nil && opts.Signal != nil { - controller := js.Global().Get("AbortController").New() - signal := controller.Get("signal") - optsMap["signal"] = signal - abort := func() { - controller.Call("abort") - } - defer abort() - defer context.AfterFunc(opts.Signal, abort)() - } - - success := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - r := new(Response) - resp := args[0] - headersIt := resp.Get("headers").Call("entries") - headers := Header{} - for { - n := headersIt.Call("next") - if n.Get("done").Bool() { - break - } - pair := n.Get("value") - key, value := pair.Index(0).String(), pair.Index(1).String() - headers.Add(key, value) - } - r.Headers = headers - r.OK = resp.Get("ok").Bool() - r.Redirected = resp.Get("redirected").Bool() - r.Status = resp.Get("status").Int() - r.StatusText = resp.Get("statusText").String() - r.Type = resp.Get("type").String() - r.URL = resp.Get("url").String() - r.BodyUsed = resp.Get("bodyUsed").Bool() - - ch <- &fetchResponse{r: r, b: resp} - return nil - }) - - defer success.Release() - - failure := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - msg := args[0].Get("message").String() - ch <- &fetchResponse{e: errors.New(msg)} - return nil - }) - defer failure.Release() - - go js.Global().Call("fetch", url, optsMap).Call("then", success).Call("catch", failure) - - r := <-ch - if r.e != nil { - return nil, r.e - } - - successBody := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - // Wrap the input ArrayBuffer with a Uint8Array - uint8arrayWrapper := js.Global().Get("Uint8Array").New(args[0]) - r.r.Body = make([]byte, uint8arrayWrapper.Get("byteLength").Int()) - js.CopyBytesToGo(r.r.Body, uint8arrayWrapper) - ch <- r - return nil - }) - defer successBody.Release() - - failureBody := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - // Assumes it's a TypeError. See - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError - // for more information on this type. - // See https://fetch.spec.whatwg.org/#concept-body-consume-body for error causes. - msg := args[0].Get("message").String() - ch <- &fetchResponse{e: errors.New(msg)} - return nil - }) - defer failureBody.Release() - - go r.b.Call("arrayBuffer").Call("then", successBody, failureBody) - - r = <-ch - return r.r, r.e -} - -// oof. -func mapOpts(opts *Opts) (map[string]interface{}, error) { - mp := map[string]interface{}{} - if opts == nil { - return mp, nil - } - - if opts.Method != "" { - mp["method"] = opts.Method - } - if opts.Header != nil { - mp["headers"] = mapHeaders(opts.Header) - } - if opts.Mode != "" { - mp["mode"] = opts.Mode - } - if opts.Credentials != "" { - mp["credentials"] = opts.Credentials - } - if opts.Cache != "" { - mp["cache"] = opts.Cache - } - if opts.Redirect != "" { - mp["redirect"] = opts.Redirect - } - if opts.Referrer != "" { - mp["referrer"] = opts.Referrer - } - if opts.ReferrerPolicy != "" { - mp["referrerPolicy"] = opts.ReferrerPolicy - } - if opts.Integrity != "" { - mp["integrity"] = opts.Integrity - } - if opts.KeepAlive != nil { - mp["keepalive"] = *opts.KeepAlive - } - - if opts.Body != nil { - bts, err := io.ReadAll(opts.Body) - if err != nil { - return nil, err - } - - mp["body"] = string(bts) - } - - return mp, nil -} - -func mapHeaders(mp Header) map[string]interface{} { - newMap := map[string]interface{}{} - for k, v := range mp { - newMap[k] = v - } - return newMap -} diff --git a/util/js-fetch/header.go b/util/js-fetch/header.go deleted file mode 100644 index 5fba170e..00000000 --- a/util/js-fetch/header.go +++ /dev/null @@ -1,160 +0,0 @@ -//go:build js - -package fetch - -import ( - "io" - "net/textproto" - "slices" - "sort" - "strings" - "sync" -) - -// A Header represents the key-value pairs in an HTTP header. -type Header map[string][]string - -// Add adds the key, value pair to the header. -// It appends to any existing values associated with key. -func (h Header) Add(key, value string) { - textproto.MIMEHeader(h).Add(key, value) -} - -// Set sets the header entries associated with key to -// the single element value. It replaces any existing -// values associated with key. -func (h Header) Set(key, value string) { - textproto.MIMEHeader(h).Set(key, value) -} - -// Get gets the first value associated with the given key. -// It is case insensitive; textproto.CanonicalMIMEHeaderKey is used -// to canonicalize the provided key. -// If there are no values associated with the key, Get returns "". -// To access multiple values of a key, or to use non-canonical keys, -// access the map directly. -func (h Header) Get(key string) string { - return textproto.MIMEHeader(h).Get(key) -} - -// get is like Get, but key must already be in CanonicalHeaderKey form. -func (h Header) get(key string) string { - if v := h[key]; len(v) > 0 { - return v[0] - } - return "" -} - -// Del deletes the values associated with key. -func (h Header) Del(key string) { - textproto.MIMEHeader(h).Del(key) -} - -// Write writes a header in wire format. -func (h Header) Write(w io.Writer) error { - return h.write(w) -} - -func (h Header) write(w io.Writer) error { - return h.writeSubset(w, nil) -} - -// Clone returns a deep copy of the header. -func (h Header) Clone() Header { - h2 := make(Header, len(h)) - for k, vv := range h { - h2[k] = slices.Clone(vv) - } - return h2 -} - -var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") - -type writeStringer interface { - WriteString(string) (int, error) -} - -// stringWriter implements WriteString on a Writer. -type stringWriter struct { - w io.Writer -} - -func (w stringWriter) WriteString(s string) (n int, err error) { - return w.w.Write([]byte(s)) -} - -type keyValues struct { - key string - values []string -} - -// A headerSorter implements sort.Interface by sorting a []keyValues -// by key. It's used as a pointer, so it can fit in a sort.Interface -// interface value without allocation. -type headerSorter struct { - kvs []keyValues -} - -func (s *headerSorter) Len() int { return len(s.kvs) } -func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] } -func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key } - -var headerSorterPool = sync.Pool{ - New: func() interface{} { return new(headerSorter) }, -} - -// sortedKeyValues returns h's keys sorted in the returned kvs -// slice. The headerSorter used to sort is also returned, for possible -// return to headerSorterCache. -func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) { - hs = headerSorterPool.Get().(*headerSorter) - if cap(hs.kvs) < len(h) { - hs.kvs = make([]keyValues, 0, len(h)) - } - kvs = hs.kvs[:0] - for k, vv := range h { - if !exclude[k] { - kvs = append(kvs, keyValues{k, vv}) - } - } - hs.kvs = kvs - sort.Sort(hs) - return kvs, hs -} - -// WriteSubset writes a header in wire format. -// If exclude is not nil, keys where exclude[key] == true are not written. -func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { - return h.writeSubset(w, exclude) -} - -func (h Header) writeSubset(w io.Writer, exclude map[string]bool) error { - ws, ok := w.(writeStringer) - if !ok { - ws = stringWriter{w} - } - kvs, sorter := h.sortedKeyValues(exclude) - for _, kv := range kvs { - for _, v := range kv.values { - v = headerNewlineToSpace.Replace(v) - v = textproto.TrimString(v) - for _, s := range []string{kv.key, ": ", v, "\r\n"} { - if _, err := ws.WriteString(s); err != nil { - headerSorterPool.Put(sorter) - return err - } - } - } - } - headerSorterPool.Put(sorter) - return nil -} - -// CanonicalHeaderKey returns the canonical format of the -// header key s. The canonicalization converts the first -// letter and any letter following a hyphen to upper case; -// the rest are converted to lowercase. For example, the -// canonical key for "accept-encoding" is "Accept-Encoding". -// If s contains a space or invalid header field bytes, it is -// returned without modifications. -func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) }