@nsnanocat/preference-panes 1.2.4 → 1.3.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/src/web.mjs DELETED
@@ -1,99 +0,0 @@
1
- import { URL } from "@nsnanocat/url";
2
- import { $app } from "@nsnanocat/util/lib/app.mjs";
3
- import { done } from "@nsnanocat/util/lib/done.mjs";
4
- import assets from "#assets";
5
-
6
- /**
7
- * 按标准 URL 语义解析页面资源地址,并限制为 HTTP(S)。
8
- * Resolve a page resource reference with standard URL semantics and require HTTP(S).
9
- * @param {string} source 资源地址 / Resource reference.
10
- * @param {URL} base 模块页面地址 / Module page URL.
11
- * @param {string} kind 资源名称 / Resource name.
12
- * @returns {URL} 绝对资源地址 / Absolute resource URL.
13
- */
14
- function resourceURL(source, base, kind) {
15
- if (!source) throw new TypeError(`${kind} resource URL is required`);
16
- const scheme = /^([a-zA-Z][a-zA-Z\d+.-]*:)/.exec(source)?.[1].toLowerCase();
17
- if (scheme && !["http:", "https:"].includes(scheme)) throw new TypeError(`${kind} resource must use HTTP(S)`);
18
- let resource;
19
- if (scheme) resource = new URL(source);
20
- else if (source.startsWith("//")) resource = new URL(`${base.protocol}${source}`);
21
- else {
22
- const [, path, query, hash] = /^([^?#]*)(\?[^#]*)?(#.*)?$/.exec(source);
23
- const pathname = path.startsWith("/") ? path : `${base.pathname.slice(0, base.pathname.lastIndexOf("/") + 1)}${path}`;
24
- const segments = [];
25
- for (const segment of pathname.split("/")) {
26
- if (segment === ".") continue;
27
- if (segment === "..") {
28
- if (segments.length > 1) segments.pop();
29
- } else segments.push(segment);
30
- }
31
- const normalized = segments.join("/") || "/";
32
- resource = new URL(`${base.origin}${normalized}${query ?? (path ? "" : base.search)}${hash ?? ""}`);
33
- }
34
- if (!["http:", "https:"].includes(resource.protocol.toLowerCase())) throw new TypeError(`${kind} resource must use HTTP(S)`);
35
- return resource;
36
- }
37
-
38
- /**
39
- * 转义写入 HTML 属性的资源地址。
40
- * Escape a resource URL for an HTML attribute.
41
- * @param {URL} resource 资源地址 / Resource URL.
42
- * @returns {string} HTML 属性值 / HTML attribute value.
43
- */
44
- function attribute(resource) {
45
- return resource.href.replaceAll("&", "&").replaceAll('"', """);
46
- }
47
-
48
- /**
49
- * 返回模块页面及其公共浏览器资源,不处理 API、网络或持久化。
50
- * Serve module pages and common browser assets without handling APIs, network access or persistence.
51
- * @returns {void} 响应已交给代理宿主 / Response delivered to the proxy host.
52
- */
53
- function run() {
54
- const request = globalThis.$request;
55
- let result;
56
- try {
57
- const url = new URL(request.url);
58
- if (/^\/settings\/[a-zA-Z0-9_-]+\/?$/.test(url.pathname)) {
59
- if (!["GET", "HEAD"].includes(request.method)) result = response(request, 405, { error: "Method not allowed" });
60
- else {
61
- const headers = Object.fromEntries(Object.entries(request.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
62
- const module = url.pathname.split("/")[2];
63
- const jsonSource = (Object.hasOwn(headers, "x-preferencepanes-json") ? headers["x-preferencepanes-json"] : (url.searchParams.get("json") ?? `/api/${module}`))?.trim();
64
- const json = `<meta name="preference-panes-boxjs" content="${attribute(resourceURL(jsonSource, url, "BoxJS"))}">`;
65
- const cssSource = (Object.hasOwn(headers, "x-preferencepanes-css") ? headers["x-preferencepanes-css"] : url.searchParams.get("css"))?.trim();
66
- let stylesheet = "";
67
- if (cssSource) stylesheet = `<link data-preference-panes-stylesheet rel="stylesheet" href="${attribute(resourceURL(cssSource, url, "CSS"))}">`;
68
- result = response(request, 200, assets.page.body.replace("<!--__PREFERENCE_PANES_JSON__-->", json).replace("<!--__PREFERENCE_PANES_STYLESHEET__-->", stylesheet), "text/html");
69
- }
70
- } else {
71
- const asset = assets[url.pathname];
72
- if (asset) result = ["GET", "HEAD"].includes(request.method) ? response(request, 200, asset.body, asset.type) : response(request, 405, { error: "Method not allowed" });
73
- }
74
- } catch (error) {
75
- console.error(`PreferencePanes Web: ${error.message}`);
76
- result = response(request, 500, { error: error.message });
77
- }
78
- if (!result) done({});
79
- else done($app === "Quantumult X" ? result : { response: result });
80
- }
81
-
82
- /**
83
- * 构造静态资源响应,HEAD 请求不返回正文。
84
- * Build a static resource response without a body for HEAD requests.
85
- * @param {import("./index.js").SettingsRequest} request 代理请求 / Proxy request.
86
- * @param {number} status HTTP 状态 / HTTP status.
87
- * @param {unknown} body 响应正文 / Response body.
88
- * @param {string} [type] 媒体类型 / Media type.
89
- * @returns {import("./index.js").SettingsResponse} 静态资源响应 / Static resource response.
90
- */
91
- function response(request, status, body, type = "application/json") {
92
- return {
93
- status,
94
- headers: { "Content-Type": `${type}; charset=utf-8`, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" },
95
- body: request.method === "HEAD" ? "" : type === "application/json" ? JSON.stringify(body) : body,
96
- };
97
- }
98
-
99
- run();