瀏覽代碼

fix(modules/web): Bug fix; Support more widely legado remoteUrl

http://1.2.4.5:5000/<path>

http://www.example.com:5000/<path>
Xwite 2 年之前
父節點
當前提交
7e78ca2f0a

+ 8 - 8
modules/web/package.json

@@ -4,8 +4,8 @@
   "private": true,
   "private": true,
   "type": "module",
   "type": "module",
   "engines": {
   "engines": {
-    "node": ">=16",
-    "pnpm": ">=8"
+    "node": ">=20",
+    "pnpm": ">=9"
   },
   },
   "scripts": {
   "scripts": {
     "dev": "vite",
     "dev": "vite",
@@ -16,13 +16,13 @@
   "dependencies": {
   "dependencies": {
     "@element-plus/icons-svg": "^2.3.1",
     "@element-plus/icons-svg": "^2.3.1",
     "@element-plus/icons-vue": "^2.3.1",
     "@element-plus/icons-vue": "^2.3.1",
-    "@vueuse/shared": "^10.11.1",
     "@vueuse/core": "^11.1.0",
     "@vueuse/core": "^11.1.0",
+    "@vueuse/shared": "^11.1.0",
     "axios": "^1.7.7",
     "axios": "^1.7.7",
     "element-plus": "^2.8.4",
     "element-plus": "^2.8.4",
     "hotkeys-js": "^3.13.7",
     "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",
     "vue-router": "^4.4.5",
     "vue3-virtual-scroll-list": "^0.2.1"
     "vue3-virtual-scroll-list": "^0.2.1"
   },
   },
@@ -34,9 +34,9 @@
     "eslint-plugin-vue": "^9.28.0",
     "eslint-plugin-vue": "^9.28.0",
     "prettier": "^3.3.3",
     "prettier": "^3.3.3",
     "sass": "^1.79.4",
     "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"
     "vite": "^5.4.8"
   }
   }
 }
 }

+ 1 - 1
modules/web/src/api/axios.js

@@ -5,7 +5,7 @@ const SECOND = 1000;
 const ajax = axios.create({
 const ajax = axios.create({
   baseURL:
   baseURL:
     import.meta.env.VITE_API ||
     import.meta.env.VITE_API ||
-    localStorage.getItem("remoteOrigin") ||
+    localStorage.getItem("remoteUrl") ||
     location.origin,
     location.origin,
   timeout: 120 * SECOND,
   timeout: 120 * SECOND,
 });
 });

+ 84 - 39
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/api */
 /** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/web */
 /** 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 {
   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) {
   } 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 !== "") {
   if (port !== "") {
-    legado_webSocket_port = Number(port) + 1;
+    legado_webSocket_port = String(Number(port) + 1);
   } else {
   } else {
     legado_webSocket_port = protocol.startsWith("https:") ? "444" : "81";
     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服务地址
 // 手动初始化 阅读web服务地址
 setLeagdoHttpUrl(ajax.defaults.baseURL);
 setLeagdoHttpUrl(ajax.defaults.baseURL);
-
+/**
+ * @param  {string|URL} http_url
+ * @returns
+ */
 const testLeagdoHttpUrlConnection = async (http_url) => {
 const testLeagdoHttpUrlConnection = async (http_url) => {
   const { data = {} } = await ajax.get("/getReadConfig", {
   const { data = {} } = await ajax.get("/getReadConfig", {
-    baseURL: http_url,
+    baseURL: new URL(http_url).toString(),
     timeout: 3000,
     timeout: 3000,
   });
   });
   // 返回结果应该是JSON 并有键值isSuccess
   // 返回结果应该是JSON 并有键值isSuccess
@@ -78,7 +120,7 @@ const saveBookProgressWithBeacon = (bookProgress) => {
   if (!bookProgress) return;
   if (!bookProgress) return;
   // 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon
   // 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon
   navigator.sendBeacon(
   navigator.sendBeacon(
-    `${legado_http_origin}/saveBookProgress`,
+    new URL("/saveBookProgress", legado_http_entry_point),
     JSON.stringify(bookProgress),
     JSON.stringify(bookProgress),
   );
   );
 };
 };
@@ -105,8 +147,9 @@ const search = (
   /** @type {(data: string) => void} */ onReceive,
   /** @type {(data: string) => void} */ onReceive,
   /** @type {() => void} */ onFinish,
   /** @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.onopen = () => {
     socket.send(`{"key":"${searchKey}"}`);
     socket.send(`{"key":"${searchKey}"}`);
@@ -150,9 +193,10 @@ const debug = (
   /** @type {(data: string) => void} */ onReceive,
   /** @type {(data: string) => void} */ onReceive,
   /** @type {() => void} */ onFinish,
   /** @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);
   const socket = new WebSocket(url);
 
 
@@ -175,8 +219,11 @@ const debug = (
  * @param {string} coverUrl
  * @param {string} coverUrl
  */
  */
 const getProxyCoverUrl = (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
  * @param {number|`${number}`} width
  */
  */
 const getProxyImageUrl = (src, 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=" +
     "/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,
   getProxyImageUrl,
 
 
   testLeagdoHttpUrlConnection,
   testLeagdoHttpUrlConnection,
-  setLeagdoHttpUrl,
-  legado_http_origin,
 };
 };

+ 1 - 0
modules/web/src/auto-imports.d.ts

@@ -3,6 +3,7 @@
 // @ts-nocheck
 // @ts-nocheck
 // noinspection JSUnusedGlobalSymbols
 // noinspection JSUnusedGlobalSymbols
 // Generated by unplugin-auto-import
 // Generated by unplugin-auto-import
+// biome-ignore lint: disable
 export {}
 export {}
 declare global {
 declare global {
   const EffectScope: typeof import('vue')['EffectScope']
   const EffectScope: typeof import('vue')['EffectScope']

+ 1 - 1
modules/web/src/components.d.ts

@@ -1,10 +1,10 @@
 /* eslint-disable */
 /* eslint-disable */
-/* prettier-ignore */
 // @ts-nocheck
 // @ts-nocheck
 // Generated by unplugin-vue-components
 // Generated by unplugin-vue-components
 // Read more: https://github.com/vuejs/core/pull/3399
 // Read more: https://github.com/vuejs/core/pull/3399
 export {}
 export {}
 
 
+/* prettier-ignore */
 declare module 'vue' {
 declare module 'vue' {
   export interface GlobalComponents {
   export interface GlobalComponents {
     BookItems: typeof import('./components/BookItems.vue')['default']
     BookItems: typeof import('./components/BookItems.vue')['default']

+ 15 - 30
modules/web/src/views/BookShelf.vue

@@ -85,7 +85,11 @@ import { useBookStore } from "@/store";
 import githubUrl from "@/assets/imgs/github.png";
 import githubUrl from "@/assets/imgs/github.png";
 import { useLoading } from "@/hooks/loading";
 import { useLoading } from "@/hooks/loading";
 import { Search as SearchIcon } from "@element-plus/icons-vue";
 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({
 export default defineComponent({
   beforeRouteEnter: (to, from, next) => {
   beforeRouteEnter: (to, from, next) => {
@@ -98,6 +102,8 @@ export default defineComponent({
             // @ts-ignore
             // @ts-ignore
             vm.saveReadConfig(data);
             vm.saveReadConfig(data);
           });
           });
+        } else {
+          next();
         }
         }
       })
       })
       .catch(() => next());
       .catch(() => next());
@@ -182,33 +188,12 @@ export default defineComponent({
         {
         {
           confirmButtonText: "确定",
           confirmButtonText: "确定",
           cancelButtonText: "取消",
           cancelButtonText: "取消",
-          inputPlaceholder: API.legado_http_origin,
-          inputValidator: (url) => {
+          inputPlaceholder: legado_http_entry_point,
+          inputValidator: (value) => {
             try {
             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) {
             } catch (e) {
-              console.warn(e);
-              return "URL解析失败,请重新输入";
+              return e?.cause?.message ?? e.message;
             }
             }
             return true;
             return true;
           },
           },
@@ -218,17 +203,17 @@ export default defineComponent({
               instance.confirmButtonLoading = true;
               instance.confirmButtonLoading = true;
               instance.confirmButtonText = "校验中……";
               instance.confirmButtonText = "校验中……";
               // instance.inputValue
               // instance.inputValue
-              const url = instance.inputValue;
+              const url = new URL(instance.inputValue);
               API.testLeagdoHttpUrlConnection(url)
               API.testLeagdoHttpUrlConnection(url)
                 //API.getBookShelf()
                 //API.getBookShelf()
                 .then(function (configStr) {
                 .then(function (configStr) {
                   saveReadConfig(configStr);
                   saveReadConfig(configStr);
                   instance.confirmButtonLoading = false;
                   instance.confirmButtonLoading = false;
                   store.setConnectType("success");
                   store.setConnectType("success");
-                  store.setConnectStatus("已连接 " + url);
                   store.clearSearchBooks();
                   store.clearSearchBooks();
                   store.setNewConnect(false);
                   store.setNewConnect(false);
-                  API.setLeagdoHttpUrl(url);
+                  setLeagdoHttpUrl(url);
+                  store.setConnectStatus("已连接 " + url.toString());
                   fetchBookShelfData();
                   fetchBookShelfData();
                   done();
                   done();
                 })
                 })
@@ -341,7 +326,7 @@ export default defineComponent({
         } else {
         } else {
           ElMessage.error(response.data.errorMsg ?? "后端返回格式错误!");
           ElMessage.error(response.data.errorMsg ?? "后端返回格式错误!");
         }
         }
-        store.setConnectStatus("已连接 " + API.legado_http_origin);
+        store.setConnectStatus("已连接 " + legado_http_entry_point);
         store.setNewConnect(false);
         store.setNewConnect(false);
       });
       });
     };
     };