diff --git a/modules/web/package.json b/modules/web/package.json index 694b6597b..837a4e710 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -4,8 +4,8 @@ "private": true, "type": "module", "engines": { - "node": ">=16", - "pnpm": ">=8" + "node": ">=20", + "pnpm": ">=9" }, "scripts": { "dev": "vite", @@ -16,13 +16,13 @@ "dependencies": { "@element-plus/icons-svg": "^2.3.1", "@element-plus/icons-vue": "^2.3.1", - "@vueuse/shared": "^10.11.1", "@vueuse/core": "^11.1.0", + "@vueuse/shared": "^11.1.0", "axios": "^1.7.7", "element-plus": "^2.8.4", "hotkeys-js": "^3.13.7", - "pinia": "^2.2.3", - "vue": "^3.5.10", + "pinia": "^2.2.4", + "vue": "^3.5.11", "vue-router": "^4.4.5", "vue3-virtual-scroll-list": "^0.2.1" }, @@ -34,9 +34,9 @@ "eslint-plugin-vue": "^9.28.0", "prettier": "^3.3.3", "sass": "^1.79.4", - "unplugin-auto-import": "^0.17.8", - "unplugin-icons": "^0.18.5", - "unplugin-vue-components": "^0.26.0", + "unplugin-auto-import": "^0.18.3", + "unplugin-icons": "^0.19.3", + "unplugin-vue-components": "^0.27.4", "vite": "^5.4.8" } } diff --git a/modules/web/src/api/axios.js b/modules/web/src/api/axios.js index 740d5cf32..7794ad9ce 100644 --- a/modules/web/src/api/axios.js +++ b/modules/web/src/api/axios.js @@ -5,7 +5,7 @@ const SECOND = 1000; const ajax = axios.create({ baseURL: import.meta.env.VITE_API || - localStorage.getItem("remoteOrigin") || + localStorage.getItem("remoteUrl") || location.origin, timeout: 120 * SECOND, }); diff --git a/modules/web/src/api/index.js b/modules/web/src/api/index.js index 49f4438e4..3a3f3b8f5 100644 --- a/modules/web/src/api/index.js +++ b/modules/web/src/api/index.js @@ -4,43 +4,85 @@ import { ElMessage } from "element-plus/es"; /** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/api */ /** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/web */ -let legado_http_origin; -let legado_webSocket_origin; +/**@type string */ +export let legado_http_entry_point; +/**@type string */ +export let legado_webSocket_entry_point; -const setLeagdoHttpUrl = (http_url) => { - let legado_webSocket_port, url; +/** + * @param {string|URL} http_url + * @returns {URL} + * @throws {Error} + */ +export const validatorHttpUrl = (http_url) => { try { - url = new URL(http_url); + const url = new URL(http_url); + if (url.toString() === legado_http_entry_point) + throw new Error("Please input different url: " + legado_http_entry_point); + const { protocol } = url; + if (!protocol.startsWith("http")) + throw new Error("Expect http:/https: protocol but " + protocol); + return url; } catch (e) { - if (localStorage.getItem("remoteOrigin") == http_url) { - localStorage.removeItem("remoteOrigin"); + if (localStorage.getItem("remoteUrl") == http_url) { + localStorage.removeItem("remoteUrl"); + console.warn("Remove remoteUrl from localStorage"); } - throw new Error("Fail to parse Leagdo remoteOrigin: " + e); + throw new Error("Fail to parse Leagdo remoteUrl " + http_url, { cause: e }); } - const { protocol, hostname, port, origin } = url; - if (!protocol.startsWith("http")) - throw new Error("unexpect protocol: " + http_url); - ajax.defaults.baseURL = origin; - //持久化 - localStorage.setItem("remoteOrigin", origin); - legado_http_origin = origin; +}; +/** + * @param {string|URL} http_url + * @returns + */ +export const setLeagdoHttpUrl = (http_url) => { + let url = new URL(location.origin); //默认当前网址的origin部分 + try { + url = validatorHttpUrl(http_url); + } catch (e) { + console.warn(e); + console.info( + "setLeagdoHttpUrl: FallBack to location.origin: " + location.origin, + ); + } + const { protocol, port, href } = url; + // websocket服务端口 为http服务端口 + 1 + let legado_webSocket_port, legado_webSocket_protocol; if (port !== "") { - legado_webSocket_port = Number(port) + 1; + legado_webSocket_port = String(Number(port) + 1); } else { legado_webSocket_port = protocol.startsWith("https:") ? "444" : "81"; } - legado_webSocket_origin = `${protocol.startsWith("https:") ? "wss://" : "ws://"}${hostname}:${legado_webSocket_port}`; + // websocket协议是否为加密版本 + legado_webSocket_protocol = protocol.startsWith("https:") + ? "wss://" + : "ws://"; - console.info("legado_server_config:"); - console.table({ legado_http_origin, legado_webSocket_origin }); + ajax.defaults.baseURL = href; + //持久化 + localStorage.setItem("remoteUrl", href); + legado_http_entry_point = href; + + url.protocol = legado_webSocket_protocol; + url.port = legado_webSocket_port; + legado_webSocket_entry_point = url.href; + + console.info("legado_api_config:"); + console.table({ + "http API入口": legado_http_entry_point, + "webSocket API入口": legado_webSocket_entry_point, + }); }; // 手动初始化 阅读web服务地址 setLeagdoHttpUrl(ajax.defaults.baseURL); - +/** + * @param {string|URL} http_url + * @returns + */ const testLeagdoHttpUrlConnection = async (http_url) => { const { data = {} } = await ajax.get("/getReadConfig", { - baseURL: http_url, + baseURL: new URL(http_url).toString(), timeout: 3000, }); // 返回结果应该是JSON 并有键值isSuccess @@ -78,7 +120,7 @@ const saveBookProgressWithBeacon = (bookProgress) => { if (!bookProgress) return; // 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon navigator.sendBeacon( - `${legado_http_origin}/saveBookProgress`, + new URL("/saveBookProgress", legado_http_entry_point), JSON.stringify(bookProgress), ); }; @@ -105,8 +147,9 @@ const search = ( /** @type {(data: string) => void} */ onReceive, /** @type {() => void} */ onFinish, ) => { - const url = `${legado_webSocket_origin}/searchBook`; - const socket = new WebSocket(url); + const socket = new WebSocket( + new URL("/searchBook", legado_webSocket_entry_point), + ); socket.onopen = () => { socket.send(`{"key":"${searchKey}"}`); @@ -150,9 +193,10 @@ const debug = ( /** @type {(data: string) => void} */ onReceive, /** @type {() => void} */ onFinish, ) => { - const url = `${legado_webSocket_origin}/${ - isBookSource ? "bookSource" : "rssSource" - }Debug`; + const url = new URL( + `/${isBookSource ? "bookSource" : "rssSource"}Debug`, + legado_webSocket_entry_point, + ); const socket = new WebSocket(url); @@ -175,8 +219,11 @@ const debug = ( * @param {string} coverUrl */ const getProxyCoverUrl = (coverUrl) => { - if (coverUrl.startsWith(legado_http_origin)) return coverUrl; - return legado_http_origin + "/cover?path=" + encodeURIComponent(coverUrl); + if (coverUrl.startsWith(legado_http_entry_point)) return coverUrl; + return new URL( + "/cover?path=" + encodeURIComponent(coverUrl), + legado_http_entry_point, + ).href; }; /** * 从阅读获取需要特定处理的图片 @@ -184,15 +231,15 @@ const getProxyCoverUrl = (coverUrl) => { * @param {number|`${number}`} width */ const getProxyImageUrl = (src, width) => { - if (src.startsWith(legado_http_origin)) return src; - return ( - legado_http_origin + + if (src.startsWith(legado_http_entry_point)) return src; + return new URL( "/image?path=" + - encodeURIComponent(src) + - "&url=" + - encodeURIComponent(sessionStorage.getItem("bookUrl")) + - "&width=" + - width + encodeURIComponent(src) + + "&url=" + + encodeURIComponent(sessionStorage.getItem("bookUrl")) + + "&width=" + + width, + legado_http_entry_point, ); }; @@ -218,6 +265,4 @@ export default { getProxyImageUrl, testLeagdoHttpUrlConnection, - setLeagdoHttpUrl, - legado_http_origin, }; diff --git a/modules/web/src/auto-imports.d.ts b/modules/web/src/auto-imports.d.ts index 992aac0c5..49274e458 100644 --- a/modules/web/src/auto-imports.d.ts +++ b/modules/web/src/auto-imports.d.ts @@ -3,6 +3,7 @@ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // Generated by unplugin-auto-import +// biome-ignore lint: disable export {} declare global { const EffectScope: typeof import('vue')['EffectScope'] diff --git a/modules/web/src/components.d.ts b/modules/web/src/components.d.ts index 56374a699..0e02ad152 100644 --- a/modules/web/src/components.d.ts +++ b/modules/web/src/components.d.ts @@ -1,10 +1,10 @@ /* eslint-disable */ -/* prettier-ignore */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 export {} +/* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { BookItems: typeof import('./components/BookItems.vue')['default'] diff --git a/modules/web/src/views/BookShelf.vue b/modules/web/src/views/BookShelf.vue index d77a89ed5..9776e26e4 100644 --- a/modules/web/src/views/BookShelf.vue +++ b/modules/web/src/views/BookShelf.vue @@ -85,7 +85,11 @@ import { useBookStore } from "@/store"; import githubUrl from "@/assets/imgs/github.png"; import { useLoading } from "@/hooks/loading"; import { Search as SearchIcon } from "@element-plus/icons-vue"; -import API from "@api"; +import API, { + legado_http_entry_point, + validatorHttpUrl, + setLeagdoHttpUrl, +} from "@api"; export default defineComponent({ beforeRouteEnter: (to, from, next) => { @@ -98,6 +102,8 @@ export default defineComponent({ // @ts-ignore vm.saveReadConfig(data); }); + } else { + next(); } }) .catch(() => next()); @@ -182,33 +188,12 @@ export default defineComponent({ { confirmButtonText: "确定", cancelButtonText: "取消", - inputPlaceholder: API.legado_http_origin, - inputValidator: (url) => { + inputPlaceholder: legado_http_entry_point, + inputValidator: (value) => { try { - const { - origin, - protocol, - username, - password, - pathname, - search, - hash, - } = new URL(url); - console.log(new URL(url)); - if (origin == API.legado_http_origin) - return "请输入非当前远程链接"; - if (!protocol.startsWith("http")) return `不支持协议${protocol}`; - if ( - pathname !== "/" || - search !== "" || - hash !== "" || - password !== "" || - username !== "" - ) - return `目前仅支持输入${origin}`; + validatorHttpUrl(value); } catch (e) { - console.warn(e); - return "URL解析失败,请重新输入"; + return e?.cause?.message ?? e.message; } return true; }, @@ -218,17 +203,17 @@ export default defineComponent({ instance.confirmButtonLoading = true; instance.confirmButtonText = "校验中……"; // instance.inputValue - const url = instance.inputValue; + const url = new URL(instance.inputValue); API.testLeagdoHttpUrlConnection(url) //API.getBookShelf() .then(function (configStr) { saveReadConfig(configStr); instance.confirmButtonLoading = false; store.setConnectType("success"); - store.setConnectStatus("已连接 " + url); store.clearSearchBooks(); store.setNewConnect(false); - API.setLeagdoHttpUrl(url); + setLeagdoHttpUrl(url); + store.setConnectStatus("已连接 " + url.toString()); fetchBookShelfData(); done(); }) @@ -341,7 +326,7 @@ export default defineComponent({ } else { ElMessage.error(response.data.errorMsg ?? "后端返回格式错误!"); } - store.setConnectStatus("已连接 " + API.legado_http_origin); + store.setConnectStatus("已连接 " + legado_http_entry_point); store.setNewConnect(false); }); };