oaspect 0.1.0
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 +19 -0
- package/bin/oaspect.mjs +127 -0
- package/dist/oaspect.js +1988 -0
- package/dist/oaspect.js.map +7 -0
- package/dist/server.js +120 -0
- package/dist/ssr.js +23873 -0
- package/package.json +70 -0
- package/src/html.js +73 -0
- package/src/serve.js +83 -0
- package/src/server.d.ts +29 -0
- package/src/server.js +168 -0
- package/src/spec.js +35 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/server.js
|
|
2
|
+
var DROPPED_HEADERS = /* @__PURE__ */ new Set([
|
|
3
|
+
"host",
|
|
4
|
+
"connection",
|
|
5
|
+
"content-length",
|
|
6
|
+
"transfer-encoding",
|
|
7
|
+
"keep-alive",
|
|
8
|
+
"upgrade",
|
|
9
|
+
"accept-encoding"
|
|
10
|
+
]);
|
|
11
|
+
var json = (body, status = 200) => Response.json(body, { status });
|
|
12
|
+
function decodeBase64(data) {
|
|
13
|
+
const binary = atob(String(data ?? ""));
|
|
14
|
+
const bytes = new Uint8Array(binary.length);
|
|
15
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
16
|
+
return bytes;
|
|
17
|
+
}
|
|
18
|
+
function formDataFromParts(parts) {
|
|
19
|
+
const data = new FormData();
|
|
20
|
+
for (const part of parts) {
|
|
21
|
+
if (part?.file) {
|
|
22
|
+
const blob = new Blob([decodeBase64(part.file.data)], { type: part.file.type || "application/octet-stream" });
|
|
23
|
+
data.append(String(part.name), blob, String(part.file.name || "file"));
|
|
24
|
+
} else {
|
|
25
|
+
data.append(String(part?.name), String(part?.value ?? ""));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
function normalizeHosts(allowedHosts) {
|
|
31
|
+
if (allowedHosts === "*") return "*";
|
|
32
|
+
return (allowedHosts ?? []).map((host) => String(host).trim().toLowerCase()).filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
function createProxyHandler({ allowedHosts, timeoutMs = 3e4, rewriteHost } = {}) {
|
|
35
|
+
const hosts = normalizeHosts(allowedHosts);
|
|
36
|
+
if (hosts !== "*" && hosts.length === 0) {
|
|
37
|
+
throw new Error('createProxyHandler: allowedHosts must list at least one host (or be "*")');
|
|
38
|
+
}
|
|
39
|
+
const allowed = (target) => hosts === "*" || hosts.includes(target.host.toLowerCase()) || hosts.includes(target.hostname.toLowerCase());
|
|
40
|
+
return async function proxy(request) {
|
|
41
|
+
if (request.method !== "POST") return json({ error: "Method not allowed." }, 405);
|
|
42
|
+
let payload;
|
|
43
|
+
try {
|
|
44
|
+
payload = await request.json();
|
|
45
|
+
} catch {
|
|
46
|
+
return json({ error: "Invalid request body." }, 400);
|
|
47
|
+
}
|
|
48
|
+
const { method = "GET", url, headers = {}, body, form } = payload ?? {};
|
|
49
|
+
let target;
|
|
50
|
+
try {
|
|
51
|
+
target = new URL(url);
|
|
52
|
+
} catch {
|
|
53
|
+
return json({ error: `Invalid URL: ${url}` }, 400);
|
|
54
|
+
}
|
|
55
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") {
|
|
56
|
+
return json({ error: "Only http and https are supported." }, 400);
|
|
57
|
+
}
|
|
58
|
+
if (!allowed(target)) return json({ error: `${target.host} is not an allowed host.` }, 403);
|
|
59
|
+
if (rewriteHost) target = rewriteHost(new URL(target));
|
|
60
|
+
const upperMethod = String(method).toUpperCase();
|
|
61
|
+
const multipart = Array.isArray(form);
|
|
62
|
+
const forwarded = Object.fromEntries(
|
|
63
|
+
Object.entries(headers).filter(([key]) => !DROPPED_HEADERS.has(key.toLowerCase()) && !(multipart && key.toLowerCase() === "content-type"))
|
|
64
|
+
);
|
|
65
|
+
const started = Date.now();
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(target, {
|
|
68
|
+
method: upperMethod,
|
|
69
|
+
headers: forwarded,
|
|
70
|
+
body: upperMethod === "GET" || upperMethod === "HEAD" ? void 0 : multipart ? formDataFromParts(form) : body,
|
|
71
|
+
redirect: "manual",
|
|
72
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
73
|
+
});
|
|
74
|
+
const text = await response.text();
|
|
75
|
+
return json({
|
|
76
|
+
status: response.status,
|
|
77
|
+
statusText: response.statusText,
|
|
78
|
+
headers: [...response.headers.entries()],
|
|
79
|
+
body: text,
|
|
80
|
+
duration: Date.now() - started
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const code = error.cause?.code ? ` (${error.cause.code})` : "";
|
|
84
|
+
return json({ error: `Could not reach ${target.origin}: ${error.message}${code}` }, 502);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function createSpecHandler({ url, fallback, cacheSeconds = 60, timeoutMs = 1e4, rewriteHost } = {}) {
|
|
89
|
+
if (!url && fallback === void 0) throw new Error("createSpecHandler: pass url, fallback or both");
|
|
90
|
+
let cached = null;
|
|
91
|
+
const respond = (text, source, contentType) => new Response(text, {
|
|
92
|
+
headers: {
|
|
93
|
+
"Content-Type": contentType ?? (text.trimStart().startsWith("{") ? "application/json; charset=utf-8" : "application/yaml; charset=utf-8"),
|
|
94
|
+
"Cache-Control": `public, max-age=${cacheSeconds}`,
|
|
95
|
+
"x-oaspect-spec-source": source
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return async function spec() {
|
|
99
|
+
if (url) {
|
|
100
|
+
if (cached && Date.now() - cached.at < cacheSeconds * 1e3) return respond(cached.text, "remote", cached.type);
|
|
101
|
+
try {
|
|
102
|
+
const target = rewriteHost ? rewriteHost(new URL(url)) : url;
|
|
103
|
+
const response = await fetch(target, { signal: AbortSignal.timeout(timeoutMs) });
|
|
104
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
105
|
+
const text2 = await response.text();
|
|
106
|
+
cached = { text: text2, type: response.headers.get("content-type") ?? void 0, at: Date.now() };
|
|
107
|
+
return respond(text2, "remote", cached.type);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (fallback === void 0) return json({ error: `Could not load ${url}: ${error.message}` }, 502);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const text = typeof fallback === "function" ? await fallback() : fallback;
|
|
113
|
+
return respond(text, url ? "fallback" : "static");
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
createProxyHandler,
|
|
118
|
+
createSpecHandler,
|
|
119
|
+
formDataFromParts
|
|
120
|
+
};
|