From 0de7334490dcefff0b847f2a083a5f73299f84e3 Mon Sep 17 00:00:00 2001 From: Kiwi2333 <1329634286@qq.com> Date: Tue, 19 Nov 2024 23:32:38 +0800 Subject: [PATCH] =?UTF-8?q?Update:=20=E9=99=A4web=E7=AB=AF=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8rust=E5=BC=80=E5=90=AFWebsocket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Chat/ContactList.vue | 7 +- composables/store/useWs.ts | 184 ++++++++++++++++++---------- init/ws.ts | 9 +- package.json | 1 + pnpm-lock.yaml | 10 ++ src-tauri/Cargo.lock | 62 ++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 5 +- src-tauri/src/desktops/mod.rs | 2 +- src-tauri/src/desktops/window.rs | 77 +++++++----- src-tauri/src/lib.rs | 2 + 11 files changed, 249 insertions(+), 111 deletions(-) diff --git a/components/Chat/ContactList.vue b/components/Chat/ContactList.vue index 20c065f2..d4c81b89 100644 --- a/components/Chat/ContactList.vue +++ b/components/Chat/ContactList.vue @@ -96,9 +96,10 @@ async function reload(size: number = 20, dto?: ContactPageDTO, isAll: boolean = pageInfo.value.cursor = null; pageInfo.value.isLast = false; pageInfo.value.size = size; - if (setting.isMobile) { // 移动端默认开启会话列表 - setting.isOpenContact = true; - setting.isOpenContactSearch = true; + if (setting.isMobile) { // 移动端 + setting.isOpenContact = true;// 打开会话列表 + setting.isOpenGroupMember = false;// 关闭群成员列表 + setting.isOpenContactSearch = true;// 打开搜索框 } await loadData(dto || props.dto); await nextTick(); diff --git a/composables/store/useWs.ts b/composables/store/useWs.ts index 137a484d..727810eb 100644 --- a/composables/store/useWs.ts +++ b/composables/store/useWs.ts @@ -1,7 +1,7 @@ import { acceptHMRUpdate, defineStore } from "pinia"; +import BackWebSocket, { type Message as BackMessage } from "@tauri-apps/plugin-websocket"; import { WsMsgBodyType, WsMsgType, WsStatusEnum } from "../types/WsType"; import type { WSFriendApply, WSMemberChange, WSMsgDelete, WSMsgRecall, WSOnlineOfflineNotify, WsMsgBodyVO, WsMsgVO, WsSendMsgDTO } from "../types/WsType"; - import type { ChatMessageVO } from "../api/chat/message"; @@ -10,7 +10,7 @@ import type { ChatMessageVO } from "../api/chat/message"; export const useWs = defineStore( "chat_websocket", () => { - const webSocketHandler = ref(null); + const webSocketHandler = ref(null); const fullWsUrl = ref(""); const isWindBlur = ref(false); const status = ref(WsStatusEnum.CLOSE); @@ -53,20 +53,61 @@ export const useWs = defineStore( const isNewMsg = computed(() => wsMsgList.value.newMsg.length > 0); // 初始化 - function initDefault(call: (event: Event) => any) { + async function initDefault(call: () => any) { + const setting = useSettingStore(); const user = useUserStore(); if (!user.getToken) { - webSocketHandler.value?.close(); + webSocketHandler.value?.close?.(); + webSocketHandler.value?.disconnect?.(); status.value = WsStatusEnum.SAFE_CLOSE; return false; } - // 1、连接 - link(BaseWSUrl, user.getToken); - // 2、打开 - open(call); - // 3、错误监听 - onerror(); - oncolse(); + if (setting.isWeb) { + // 1、连接 + link(BaseWSUrl, user.getToken); + // 2、打开 + if (!webSocketHandler.value) + return; + webSocketHandler.value.onopen = call; + // 3、错误监听 + webSocketHandler?.value?.addEventListener("error", (e: Event) => { + status.value = WsStatusEnum.CLOSE; + webSocketHandler.value = null; + console.log(e); + }); + // 4、关闭监听 + webSocketHandler.value.addEventListener("close", (e: Event) => { + status.value = WsStatusEnum.SAFE_CLOSE; + webSocketHandler.value = null; + }); + return true; + } + + // 移动端 + const url = BaseWSUrl; + if (webSocketHandler.value && status.value === WsStatusEnum.OPEN) + return webSocketHandler.value; + fullWsUrl.value = `${url}?Authorization=${user.getToken}`; + const ws = await BackWebSocket.connect(fullWsUrl.value); + webSocketHandler.value = ws; // 保存连接 + // 1、打开 + status.value = ws.id ? WsStatusEnum.OPEN : WsStatusEnum.CLOSE; + if (ws.id) + call(); + // 2、错误监听 + webSocketHandler.value.addListener((msg: BackMessage) => { + console.log("Rust Received Message:", msg); + if (msg.type === "Close") { + status.value = WsStatusEnum.SAFE_CLOSE; + webSocketHandler.value = null; + } + // else if (msg.type === "Ping") { + // // 心跳响应 + // } + // else if (msg.type === "Pong") { + // // 心跳请求 + // } + }); } // 链接 @@ -79,32 +120,6 @@ export const useWs = defineStore( return webSocketHandler.value; } - // 打开 - function open(call: (event: Event) => void) { - if (!webSocketHandler.value) - return; - webSocketHandler.value.onopen = call; - } - - // 错误重试 - function onerror() { - if (!webSocketHandler.value) - return; - webSocketHandler.value.addEventListener("error", (e) => { - status.value = WsStatusEnum.CLOSE; - webSocketHandler.value = null; - console.log(e); - }); - } - - function oncolse() { - if (!webSocketHandler.value) - return; - webSocketHandler.value.addEventListener("close", (e) => { - status.value = WsStatusEnum.SAFE_CLOSE; - webSocketHandler.value = null; - }); - } const wsMsgMap = { [WsMsgBodyType.MESSAGE]: "newMsg", @@ -120,35 +135,66 @@ export const useWs = defineStore( function onMessage(call: (data: WsMsgBodyVO) => void) { if (!webSocketHandler.value) return; - webSocketHandler.value.addEventListener("message", (event) => { - if (event && !event.data) - return false; + const setting = useSettingStore(); + if (setting.isWeb) { + webSocketHandler.value.addEventListener("message", (event: MessageEvent) => { + if (event && !event.data) + return false; - // 这里需要处理一下,因为有时候会收到空数据 - try { - const data = JSON.parse(event.data); - if (data) { - const cts = data.data as WsMsgBodyVO; - const body = cts.data; - if (wsMsgMap[cts.type] !== undefined) - // @ts-expect-error - wsMsgList.value[wsMsgMap[cts.type]].push(body as any); - call(cts); + // 这里需要处理一下,因为有时候会收到空数据 + try { + const data = JSON.parse(event.data); + if (data) { + const cts = data.data as WsMsgBodyVO; + const body = cts.data; + if (wsMsgMap[cts.type] !== undefined) + // @ts-expect-error + wsMsgList.value[wsMsgMap[cts.type]].push(body as any); + call(cts); + } } - } - catch (err) { - return null; - } - }); - } + catch (err) { + return null; + } + }); + } + else { // 移动端 + webSocketHandler.value.addListener((msg: BackMessage) => { + if (msg.type === "Text") { + if (!msg.data) + return false; + // 这里需要处理一下,因为有时候会收到空数据 + try { + const data = JSON.parse(String(msg.data)); + if (data) { + const cts = data.data as WsMsgBodyVO; + const body = cts.data; + if (wsMsgMap[cts.type] !== undefined) + // @ts-expect-error + wsMsgList.value[wsMsgMap[cts.type]].push(body as any); + call(cts); + } + } + catch (err) { + return null; + } + } + }); + } + } // 关闭 function close(isConfirm = true) { if (!webSocketHandler.value) return; if (!isConfirm) { - webSocketHandler.value.close(); + try { + webSocketHandler.value?.close?.(); + webSocketHandler.value?.disconnect?.(); + } + catch (err) { + } status.value = WsStatusEnum.SAFE_CLOSE; return; } @@ -162,7 +208,12 @@ export const useWs = defineStore( if (res === "confirm") { if (!webSocketHandler.value) return; - webSocketHandler.value.close(); + try { + webSocketHandler.value?.close?.(); + webSocketHandler.value?.disconnect?.(); + } + catch (err) { + } status.value = WsStatusEnum.SAFE_CLOSE; ElNotification.success("断开成功!"); } @@ -184,13 +235,18 @@ export const useWs = defineStore( * @param dto 参数 */ function send(dto: WsSendMsgDTO) { - if (webSocketHandler.value?.OPEN) - webSocketHandler.value?.send(JSON.stringify(dto)); + if (webSocketHandler.value?.OPEN) { + const setting = useSettingStore(); + if (setting.isWeb) + webSocketHandler.value?.send(JSON.stringify(dto)); + else // 移动端 + webSocketHandler.value?.send(JSON.stringify(dto)); + } } // 清除 function resetStore() { - webSocketHandler?.value?.close(); + close(false); wsMsgList.value = { newMsg: [], onlineNotice: [], @@ -220,8 +276,6 @@ export const useWs = defineStore( reload, onerror, initDefault, - link, - open, send, close, sendHeart, @@ -236,7 +290,3 @@ export const useWs = defineStore( if (import.meta.hot) import.meta.hot.accept(acceptHMRUpdate(useWs, import.meta.hot)); - -interface WsMsgTypeMap { - [key: string]: T[] -} diff --git a/init/ws.ts b/init/ws.ts index f2b05b51..83a30ff5 100644 --- a/init/ws.ts +++ b/init/ws.ts @@ -17,14 +17,13 @@ export async function useWsInit() { // 创建 Web Worker let worker: Worker; // 初始化 Web Worker - function reload() { - // web端 + async function reload() { worker?.removeEventListener?.("message", (e) => { }); worker?.terminate?.(); // 关闭 WebSocket 连接 worker = new Worker("useWsWorker.js"); // 初始化 WebSocket 连接 - ws.initDefault((e) => { - // 将 WebSocket 状态和noticeType发送给 Web Worker 初始化状态 + ws.initDefault(() => { + // 将 WebSocket 状态和noticeType发送给 Web Worker 初始化状态 worker.postMessage({ status: ws.status, noticeType, @@ -59,7 +58,7 @@ export async function useWsInit() { if (val[0] !== WsStatusEnum.OPEN && val[1]) reload(); else if (!val[1]) - ws.webSocketHandler?.close(); + ws.webSocketHandler?.disconnect?.(); }, { debounce: 1000, immediate: false, diff --git a/package.json b/package.json index 0273875d..c43ae12e 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@tauri-apps/plugin-dialog": "~2", "@tauri-apps/plugin-fs": "~2.0.2", "@tauri-apps/plugin-upload": "~2.1.0", + "@tauri-apps/plugin-websocket": "~2", "@tauri-apps/plugin-window-state": "~2", "currency.js": "2.0.4", "element-china-area-data": "6.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4b42500..99dc03ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@tauri-apps/plugin-upload': specifier: ~2.1.0 version: 2.1.0 + '@tauri-apps/plugin-websocket': + specifier: ~2 + version: 2.0.0 '@tauri-apps/plugin-window-state': specifier: ~2 version: 2.0.0 @@ -1541,6 +1544,9 @@ packages: '@tauri-apps/plugin-upload@2.1.0': resolution: {integrity: sha512-nSIyxp2sAHsj+1RMs3obTP2lC1rzeWcMxHtzLYjnXLnJMXvWlFG0T7jEZ9Sg/OErvQxPVIvDW+12evuFKa5t8Q==} + '@tauri-apps/plugin-websocket@2.0.0': + resolution: {integrity: sha512-O2qRxZCljd4g+ceJhW7LfgQr+fg0fBBiAaLiMopoKL6TXKMnhBHOenp4nZ5/MoVTr77OQIDNO6Jp/c1YwiRVtQ==} + '@tauri-apps/plugin-window-state@2.0.0': resolution: {integrity: sha512-O82iRlrh1BLgBI8CTc+NMTPxQhQo8II5admKq9mLvH45Us5i4Zcr74At6eM46nOflFd7R8bZsVNGy+PxOEqUmQ==} @@ -7319,6 +7325,10 @@ snapshots: dependencies: '@tauri-apps/api': 2.1.1 + '@tauri-apps/plugin-websocket@2.0.0': + dependencies: + '@tauri-apps/api': 2.1.1 + '@tauri-apps/plugin-window-state@2.0.0': dependencies: '@tauri-apps/api': 2.1.1 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 177144ed..04367ba6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -816,6 +816,12 @@ dependencies = [ "syn 2.0.85", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "deranged" version = "0.3.11" @@ -1973,6 +1979,7 @@ dependencies = [ "tauri-plugin-single-instance", "tauri-plugin-updater", "tauri-plugin-upload", + "tauri-plugin-websocket", "tauri-plugin-window-state", ] @@ -4300,6 +4307,25 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tauri-plugin-websocket" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2793b69e1dd494beed1e0a29865f38bd00011e7ccc35a3cfde8e3939328b790" +dependencies = [ + "futures-util", + "http", + "log", + "rand 0.8.5", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror", + "tokio", + "tokio-tungstenite", +] + [[package]] name = "tauri-plugin-window-state" version = "2.0.1" @@ -4541,6 +4567,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -4677,6 +4719,26 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.5", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5b71d39b..537521d1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -28,6 +28,7 @@ tauri-plugin-fs = "2" tauri-plugin-upload = "2" tauri-plugin-dialog = "2" tauri-plugin-process = "2" +tauri-plugin-websocket = "2" [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] tauri-plugin-autostart = "2" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 81aa71ec..2f782ce0 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -66,7 +66,10 @@ "notification:default", "notification:allow-get-active", "notification:allow-is-permission-granted", - "dialog:default" + "dialog:default", + "websocket:default", + "websocket:allow-connect", + "websocket:allow-send" ], "platforms": [ "windows", diff --git a/src-tauri/src/desktops/mod.rs b/src-tauri/src/desktops/mod.rs index 5240f841..e839df15 100644 --- a/src-tauri/src/desktops/mod.rs +++ b/src-tauri/src/desktops/mod.rs @@ -1,2 +1,2 @@ -pub mod window; pub mod tray; +pub mod window; diff --git a/src-tauri/src/desktops/window.rs b/src-tauri/src/desktops/window.rs index 48de23d4..773fc9b0 100644 --- a/src-tauri/src/desktops/window.rs +++ b/src-tauri/src/desktops/window.rs @@ -14,16 +14,17 @@ pub fn setup_desktop_window(app: &AppHandle) -> tauri::Result<()> { // 消息窗口配置 #[cfg(desktop)] - let mut msgbox_builder = WebviewWindowBuilder::new(app, "msgbox", WebviewUrl::App("/msg".into())) - .title("消息通知") - .inner_size(240.0, 300.0) - .skip_taskbar(true) - .decorations(false) - .resizable(false) - .always_on_top(true) - .shadow(false) - .position(-240.0,-300.0) - .visible(false); + let mut msgbox_builder = + WebviewWindowBuilder::new(app, "msgbox", WebviewUrl::App("/msg".into())) + .title("消息通知") + .inner_size(240.0, 300.0) + .skip_taskbar(true) + .decorations(false) + .resizable(false) + .always_on_top(true) + .shadow(false) + .position(-240.0, -300.0) + .visible(false); // Windows 和 Linux 平台特定配置 #[cfg(any(target_os = "windows", target_os = "linux"))] @@ -42,33 +43,39 @@ pub fn setup_desktop_window(app: &AppHandle) -> tauri::Result<()> { // 构建主窗口和消息窗口 let main_window = main_builder.build()?; - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] let msgbox_window = msgbox_builder.build()?; // 监听窗口事件 - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] - msgbox_window.clone().on_window_event(move |event| match event { - WindowEvent::CloseRequested { api, .. } => { - println!("关闭请求,窗口将最小化而不是关闭。"); - api.prevent_close(); - } - WindowEvent::Focused(focused) => { - if !*focused { - msgbox_window.hide().unwrap_or_else(|e| eprintln!("隐藏窗口时出错: {:?}", e)); + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + msgbox_window + .clone() + .on_window_event(move |event| match event { + WindowEvent::CloseRequested { api, .. } => { + println!("关闭请求,窗口将最小化而不是关闭。"); + api.prevent_close(); } - } - _ => {} - }); + WindowEvent::Focused(focused) => { + if !*focused { + msgbox_window + .hide() + .unwrap_or_else(|e| eprintln!("隐藏窗口时出错: {:?}", e)); + } + } + _ => {} + }); // 监听窗口事件 - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] - main_window.clone().on_window_event(move |event| match event { - WindowEvent::CloseRequested { api, .. } => { - println!("关闭请求,窗口将最小化而不是关闭。"); - api.prevent_close(); - } - _ => {} - }); - + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + main_window + .clone() + .on_window_event(move |event| match event { + WindowEvent::CloseRequested { api, .. } => { + println!("关闭请求,窗口将最小化而不是关闭。"); + api.prevent_close(); + } + _ => {} + }); + // 仅在构建 macOS 时设置背景颜色 #[cfg(target_os = "macos")] { @@ -93,9 +100,11 @@ pub fn setup_desktop_window(app: &AppHandle) -> tauri::Result<()> { #[cfg(desktop)] pub fn show_window(app: &AppHandle) { if let Some(window) = app.webview_windows().values().next() { - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] { - window.show().unwrap_or_else(|e| eprintln!("显示窗口时出错: {:?}", e)); + window + .show() + .unwrap_or_else(|e| eprintln!("显示窗口时出错: {:?}", e)); } } else { eprintln!("未找到窗口"); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1237e71a..2d169488 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -52,6 +52,7 @@ impl AppBuilder { fn setup_mobile() { println!("App from Mobile!"); tauri::Builder::default() + .plugin(tauri_plugin_websocket::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_upload::init()) @@ -75,6 +76,7 @@ fn setup_mobile() { fn setup_desktop() { println!("App from Desktop!"); tauri::Builder::default() + .plugin(tauri_plugin_websocket::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init())