@mygo-plugins/fetch 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +142 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MyGo contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @mygo-plugins/fetch
|
|
2
|
+
|
|
3
|
+
A `fetch` for [MyGo](https://github.com/egoist/mygo) apps that makes HTTP
|
|
4
|
+
requests from Go: not subject to CORS, free to set any header (Origin,
|
|
5
|
+
Cookie, User-Agent, ...), with the standard options and a standard
|
|
6
|
+
`Response` whose body streams as Go reads it.
|
|
7
|
+
|
|
8
|
+
Use the plugin in Go:
|
|
9
|
+
|
|
10
|
+
```go
|
|
11
|
+
import "github.com/egoist/mygo/plugins/fetch"
|
|
12
|
+
|
|
13
|
+
mygo.Use(fetch.Plugin) // or fetch.New(fetch.Options{Client: ..., Allow: ...})
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
and fetch in the frontend:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { fetch } from "@mygo-plugins/fetch";
|
|
20
|
+
|
|
21
|
+
const ctrl = new AbortController();
|
|
22
|
+
const res = await fetch("https://api.example.com/items", {
|
|
23
|
+
headers: { authorization: `Bearer ${token}` },
|
|
24
|
+
signal: ctrl.signal, // aborting cancels the request in Go
|
|
25
|
+
});
|
|
26
|
+
const items = await res.json();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Aborting the signal, canceling the body, navigating away or closing the
|
|
30
|
+
window cancels the request in Go.
|
|
31
|
+
- `redirect: "manual"` returns the redirect response itself, with its
|
|
32
|
+
`Location`, instead of an opaque response.
|
|
33
|
+
- `mode`, `credentials`, `cache` and the like do not apply; cookies are those
|
|
34
|
+
of the Go side's `http.Client` (give it a `Jar` to keep them).
|
|
35
|
+
- Every response header is readable but `Set-Cookie`, which a `Response`
|
|
36
|
+
cannot hold.
|
|
37
|
+
- URLs that are not http or https (the app's own pages, `data:`, `blob:`) go
|
|
38
|
+
to the webview's `fetch`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Makes an HTTP request from Go, with the options and result of the
|
|
3
|
+
* standard `fetch`. `mode`, `credentials`, `cache` and the like have no
|
|
4
|
+
* meaning there and are ignored; cookies are those of the Go side's
|
|
5
|
+
* `http.Client`. Response headers are all there but `Set-Cookie`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
8
|
+
export default fetch;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Channel, call, isCallError } from "mygo-runtime";
|
|
3
|
+
var NULL_BODY = new Set([101, 103, 204, 205, 304]);
|
|
4
|
+
var NORMALIZED = new Set(["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"]);
|
|
5
|
+
async function fetch(input, init = {}) {
|
|
6
|
+
const req = typeof Request !== "undefined" && input instanceof Request ? input : null;
|
|
7
|
+
const base = typeof location !== "undefined" ? location.href : undefined;
|
|
8
|
+
const url = new URL(req ? req.url : input instanceof URL ? input.href : String(input), base);
|
|
9
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
10
|
+
return globalThis.fetch(input, init);
|
|
11
|
+
const signal = init.signal ?? req?.signal ?? null;
|
|
12
|
+
signal?.throwIfAborted();
|
|
13
|
+
let method = init.method ?? req?.method ?? "GET";
|
|
14
|
+
if (NORMALIZED.has(method.toUpperCase()))
|
|
15
|
+
method = method.toUpperCase();
|
|
16
|
+
const headers = new Headers(init.headers ?? req?.headers);
|
|
17
|
+
let body = null;
|
|
18
|
+
if (init.body != null) {
|
|
19
|
+
const r = new Response(init.body);
|
|
20
|
+
const type = r.headers.get("content-type");
|
|
21
|
+
if (type && !headers.has("content-type"))
|
|
22
|
+
headers.set("content-type", type);
|
|
23
|
+
body = new Uint8Array(await r.arrayBuffer());
|
|
24
|
+
} else if (init.body === undefined && req?.body) {
|
|
25
|
+
body = new Uint8Array(await req.arrayBuffer());
|
|
26
|
+
}
|
|
27
|
+
if (body && (method === "GET" || method === "HEAD")) {
|
|
28
|
+
throw new TypeError(`fetch: a ${method} request cannot have a body`);
|
|
29
|
+
}
|
|
30
|
+
const parts = new Channel;
|
|
31
|
+
const done = call("plugin:fetch.Fetch", {
|
|
32
|
+
url: url.href,
|
|
33
|
+
method,
|
|
34
|
+
headers: [...headers],
|
|
35
|
+
body: body && toBase64(body),
|
|
36
|
+
redirect: init.redirect ?? req?.redirect ?? "follow"
|
|
37
|
+
}, parts);
|
|
38
|
+
const failed = done.then(() => null, (err) => failure(err));
|
|
39
|
+
const it = parts[Symbol.asyncIterator]();
|
|
40
|
+
let stream;
|
|
41
|
+
let rejectHead;
|
|
42
|
+
const onAbort = () => {
|
|
43
|
+
parts.close();
|
|
44
|
+
try {
|
|
45
|
+
stream?.error(signal.reason);
|
|
46
|
+
} catch {}
|
|
47
|
+
rejectHead?.(signal.reason);
|
|
48
|
+
};
|
|
49
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
50
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
51
|
+
let head;
|
|
52
|
+
try {
|
|
53
|
+
const first = await new Promise((resolve, reject) => {
|
|
54
|
+
rejectHead = reject;
|
|
55
|
+
it.next().then(resolve, reject);
|
|
56
|
+
});
|
|
57
|
+
rejectHead = undefined;
|
|
58
|
+
if (first.done)
|
|
59
|
+
throw await failed ?? new TypeError("fetch failed: no response");
|
|
60
|
+
head = first.value;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
cleanup();
|
|
63
|
+
parts.close();
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
const status = head.status ?? 200;
|
|
67
|
+
const nullBody = NULL_BODY.has(status) || method === "HEAD";
|
|
68
|
+
const readable = nullBody ? null : new ReadableStream({
|
|
69
|
+
start(controller) {
|
|
70
|
+
stream = controller;
|
|
71
|
+
},
|
|
72
|
+
async pull(controller) {
|
|
73
|
+
const next = await it.next();
|
|
74
|
+
if (signal?.aborted)
|
|
75
|
+
return;
|
|
76
|
+
if (!next.done) {
|
|
77
|
+
if (next.value.chunk)
|
|
78
|
+
controller.enqueue(fromBase64(next.value.chunk));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
cleanup();
|
|
82
|
+
const err = await failed;
|
|
83
|
+
if (err)
|
|
84
|
+
controller.error(err);
|
|
85
|
+
else
|
|
86
|
+
controller.close();
|
|
87
|
+
},
|
|
88
|
+
cancel() {
|
|
89
|
+
cleanup();
|
|
90
|
+
parts.close();
|
|
91
|
+
}
|
|
92
|
+
}, { highWaterMark: 0 });
|
|
93
|
+
if (nullBody) {
|
|
94
|
+
parts.close();
|
|
95
|
+
cleanup();
|
|
96
|
+
}
|
|
97
|
+
const valid = status >= 200 && status <= 599;
|
|
98
|
+
const res = new Response(readable, {
|
|
99
|
+
status: valid ? status : 200,
|
|
100
|
+
statusText: head.statusText ?? "",
|
|
101
|
+
headers: head.headers ?? []
|
|
102
|
+
});
|
|
103
|
+
const own = (name, value) => Object.defineProperty(res, name, { value, enumerable: true });
|
|
104
|
+
own("url", head.url ?? url.href);
|
|
105
|
+
own("redirected", head.redirected ?? false);
|
|
106
|
+
if (!valid)
|
|
107
|
+
own("status", status);
|
|
108
|
+
return res;
|
|
109
|
+
}
|
|
110
|
+
var src_default = fetch;
|
|
111
|
+
function failure(err) {
|
|
112
|
+
if (!isCallError(err))
|
|
113
|
+
return err instanceof Error ? err : new TypeError(String(err));
|
|
114
|
+
if (err.message.includes("plugin is not used")) {
|
|
115
|
+
return new Error("@mygo-plugins/fetch: the Go side does not use the plugin: call mygo.Use(fetch.Plugin) before App.Run");
|
|
116
|
+
}
|
|
117
|
+
return new TypeError(`fetch failed: ${err.message}`, { cause: err });
|
|
118
|
+
}
|
|
119
|
+
function toBase64(bytes) {
|
|
120
|
+
const native = bytes.toBase64;
|
|
121
|
+
if (native)
|
|
122
|
+
return native.call(bytes);
|
|
123
|
+
let s = "";
|
|
124
|
+
for (let i = 0;i < bytes.length; i += 32768) {
|
|
125
|
+
s += String.fromCharCode.apply(null, bytes.subarray(i, i + 32768));
|
|
126
|
+
}
|
|
127
|
+
return btoa(s);
|
|
128
|
+
}
|
|
129
|
+
function fromBase64(s) {
|
|
130
|
+
const native = Uint8Array.fromBase64;
|
|
131
|
+
if (native)
|
|
132
|
+
return native(s);
|
|
133
|
+
const bin = atob(s);
|
|
134
|
+
const bytes = new Uint8Array(bin.length);
|
|
135
|
+
for (let i = 0;i < bin.length; i++)
|
|
136
|
+
bytes[i] = bin.charCodeAt(i);
|
|
137
|
+
return bytes;
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
src_default as default,
|
|
141
|
+
fetch
|
|
142
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mygo-plugins/fetch",
|
|
3
|
+
"version": "0.1.8",
|
|
4
|
+
"description": "A fetch that makes HTTP requests from Go, for MyGo desktop apps",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mygo",
|
|
7
|
+
"desktop",
|
|
8
|
+
"webview",
|
|
9
|
+
"go",
|
|
10
|
+
"fetch"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/egoist/mygo.git",
|
|
15
|
+
"directory": "plugins/fetch"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "bun run ../build.ts",
|
|
33
|
+
"test": "bun test"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"mygo-runtime": "^0.1.8"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"mygo-runtime": "^0.1.8"
|
|
40
|
+
}
|
|
41
|
+
}
|