mirror of
https://github.com/Xinrea/bili-shadowreplay.git
synced 2025-11-24 20:15:34 +08:00
feat: migrate sse to websocket
This commit is contained in:
@@ -30,7 +30,8 @@
|
||||
"@tauri-apps/plugin-sql": "~2",
|
||||
"lucide-svelte": "^0.479.0",
|
||||
"marked": "^16.1.1",
|
||||
"qrcode": "^1.5.4"
|
||||
"qrcode": "^1.5.4",
|
||||
"socket.io-client": "^4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.0",
|
||||
|
||||
154
src-tauri/Cargo.lock
generated
154
src-tauri/Cargo.lock
generated
@@ -124,6 +124,15 @@ version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.11.0"
|
||||
@@ -442,7 +451,7 @@ dependencies = [
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"matchit 0.7.3",
|
||||
"memchr",
|
||||
"mime",
|
||||
"multer",
|
||||
@@ -575,6 +584,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"simplelog",
|
||||
"socketioxide",
|
||||
"sqlx",
|
||||
"srtparse",
|
||||
"sysinfo",
|
||||
@@ -592,6 +602,7 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"toml 0.7.8",
|
||||
"tower-http 0.5.2",
|
||||
@@ -1313,7 +1324,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-tungstenite 0.27.0",
|
||||
"tonic-build",
|
||||
"url",
|
||||
"urlencoding",
|
||||
@@ -1539,6 +1550,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
@@ -1777,6 +1799,45 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
|
||||
|
||||
[[package]]
|
||||
name = "engineioxide"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88a4ef9fd57bc7e9fbe59550d3cba88536fbe47fba05ab33088edc4b09d3267a"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"engineioxide-core",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.26.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "engineioxide-core"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04e5d58eb7374df380cbb53ef65f9c35f544c9c217528adb1458c8df05978475"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"rand 0.9.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.6.4"
|
||||
@@ -3509,6 +3570,12 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
@@ -5790,6 +5857,58 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socketioxide"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "476190583b592f1e3d55584269600f2d3c4f18af36adad03c41c27e82dcb6bd5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"engineioxide",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.6.0",
|
||||
"matchit 0.8.6",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"socketioxide-core",
|
||||
"socketioxide-parser-common",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socketioxide-core"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b07b95089a961994921d23dd6e70792a06f5daa250b5ec8919f6f9de371d2cc5"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"bytes",
|
||||
"engineioxide-core",
|
||||
"futures-core",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socketioxide-parser-common"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fe3b57122bf9c17fe8c2f364e1d307983068396cfb1b0407ec897de411f8033"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"socketioxide-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "softbuffer"
|
||||
version = "0.4.6"
|
||||
@@ -7009,6 +7128,18 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite 0.26.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.27.0"
|
||||
@@ -7020,7 +7151,7 @@ dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tungstenite",
|
||||
"tungstenite 0.27.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7296,6 +7427,23 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.1",
|
||||
"sha1",
|
||||
"thiserror 2.0.12",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.27.0"
|
||||
|
||||
@@ -55,12 +55,14 @@ tower-http = { version = "0.5", features = ["cors", "fs"] }
|
||||
futures-core = "0.3"
|
||||
futures = "0.3"
|
||||
tokio-util = { version = "0.7", features = ["io"] }
|
||||
tokio-stream = "0.1"
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
url = "2.5.4"
|
||||
srtparse = "0.2.0"
|
||||
thiserror = "2"
|
||||
deno_core = "0.355"
|
||||
sanitize-filename = "0.6.0"
|
||||
socketioxide = "0.17.2"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
@@ -35,7 +38,7 @@ use crate::{
|
||||
},
|
||||
AccountInfo,
|
||||
},
|
||||
progress::progress_manager::Event,
|
||||
http_server::websocket,
|
||||
recorder::{
|
||||
bilibili::{
|
||||
client::{QrInfo, QrStatus},
|
||||
@@ -48,19 +51,15 @@ use crate::{
|
||||
recorder_manager::{ClipRangeParams, RecorderList},
|
||||
state::State,
|
||||
};
|
||||
use axum::extract::Query;
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{DefaultBodyLimit, Json, Multipart, Path},
|
||||
http::{Request, StatusCode},
|
||||
middleware::{self, Next},
|
||||
response::{IntoResponse, Response, Sse},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use axum::{extract::Query, response::sse};
|
||||
use futures::stream::{self, Stream};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
@@ -1628,67 +1627,6 @@ async fn handler_hls(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
// 字符串转义工具函数
|
||||
fn escape_sse_string(s: &str) -> String {
|
||||
s.replace('\\', "\\\\")
|
||||
.replace('\n', "\\n")
|
||||
.replace('\r', "\\r")
|
||||
.replace('"', "\\\"")
|
||||
}
|
||||
|
||||
async fn handler_sse(
|
||||
state: axum::extract::State<State>,
|
||||
) -> Sse<impl Stream<Item = Result<sse::Event, axum::Error>>> {
|
||||
let rx = state.progress_manager.subscribe();
|
||||
|
||||
let stream = stream::unfold(rx, move |mut rx| async move {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
let sse_event = match event {
|
||||
Event::ProgressUpdate { id, content } => {
|
||||
sse::Event::default().event("progress-update").data(format!(
|
||||
r#"{{"id":"{}","content":"{}"}}"#,
|
||||
id,
|
||||
escape_sse_string(&content)
|
||||
))
|
||||
}
|
||||
Event::ProgressFinished {
|
||||
id,
|
||||
success,
|
||||
message,
|
||||
} => sse::Event::default()
|
||||
.event("progress-finished")
|
||||
.data(format!(
|
||||
r#"{{"id":"{}","success":{},"message":"{}"}}"#,
|
||||
id,
|
||||
success,
|
||||
escape_sse_string(&message)
|
||||
)),
|
||||
Event::DanmuReceived { room, ts, content } => sse::Event::default()
|
||||
.event(format!("danmu:{}", room))
|
||||
.data(format!(
|
||||
r#"{{"ts":"{}","content":"{}"}}"#,
|
||||
ts,
|
||||
escape_sse_string(&content)
|
||||
)),
|
||||
};
|
||||
Some((Ok(sse_event), rx))
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Closed) => None,
|
||||
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
|
||||
// 跳过丢失的事件,继续处理
|
||||
Some((Ok(sse::Event::default().event("keep-alive").data("")), rx))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Sse::new(stream).keep_alive(
|
||||
sse::KeepAlive::new()
|
||||
.interval(std::time::Duration::from_secs(30))
|
||||
.text("keep-alive"),
|
||||
)
|
||||
}
|
||||
|
||||
const MAX_BODY_SIZE: usize = 10 * 1024 * 1024 * 1024;
|
||||
|
||||
pub async fn start_api_server(state: State) {
|
||||
@@ -1864,11 +1802,13 @@ pub async fn start_api_server(state: State) {
|
||||
.route("/api/upload_file", post(handler_upload_file))
|
||||
.route("/api/image/:video_id", get(handler_image_base64))
|
||||
.route("/hls/*uri", get(handler_hls))
|
||||
.route("/api/sse", get(handler_sse))
|
||||
.nest_service("/output", ServeDir::new(output_path))
|
||||
.nest_service("/cache", ServeDir::new(cache_path));
|
||||
|
||||
let websocket_layer = websocket::create_websocket_server(state.clone()).await;
|
||||
|
||||
let router = app
|
||||
.layer(websocket_layer)
|
||||
.layer(cors)
|
||||
.layer(DefaultBodyLimit::max(MAX_BODY_SIZE))
|
||||
.with_state(state);
|
||||
2
src-tauri/src/http_server/mod.rs
Normal file
2
src-tauri/src/http_server/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod api_server;
|
||||
pub mod websocket;
|
||||
101
src-tauri/src/http_server/websocket.rs
Normal file
101
src-tauri/src/http_server/websocket.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use serde_json::{json, Value};
|
||||
use socketioxide::{
|
||||
extract::{Data, SocketRef},
|
||||
layer::SocketIoLayer,
|
||||
SocketIo,
|
||||
};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::progress::progress_manager::Event;
|
||||
use crate::state::State;
|
||||
|
||||
pub async fn create_websocket_server(state: State) -> SocketIoLayer {
|
||||
let (layer, io) = SocketIo::new_layer();
|
||||
|
||||
// Clone the state for the namespace handler
|
||||
let state_clone = state.clone();
|
||||
|
||||
io.ns("/ws", move |socket: SocketRef| {
|
||||
let state = state_clone.clone();
|
||||
|
||||
// Subscribe to progress events
|
||||
let mut rx = state.progress_manager.subscribe();
|
||||
|
||||
// Spawn a task to handle progress events for this socket
|
||||
let socket_clone = socket.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
let (event_type, message) = match event {
|
||||
Event::ProgressUpdate { id, content } => (
|
||||
"progress",
|
||||
json!({
|
||||
"event": "progress-update",
|
||||
"data": {
|
||||
"id": id,
|
||||
"content": content
|
||||
}
|
||||
}),
|
||||
),
|
||||
Event::ProgressFinished {
|
||||
id,
|
||||
success,
|
||||
message,
|
||||
} => (
|
||||
"progress",
|
||||
json!({
|
||||
"event": "progress-finished",
|
||||
"data": {
|
||||
"id": id,
|
||||
"success": success,
|
||||
"message": message
|
||||
}
|
||||
}),
|
||||
),
|
||||
Event::DanmuReceived { room, ts, content } => (
|
||||
"danmu",
|
||||
json!({
|
||||
"event": "danmu-received",
|
||||
"data": {
|
||||
"room": room,
|
||||
"ts": ts,
|
||||
"content": content
|
||||
}
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
if let Err(e) = socket_clone.emit(event_type, &message) {
|
||||
log::warn!("Failed to emit progress event to WebSocket client: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
log::info!("Progress channel closed, stopping WebSocket progress stream");
|
||||
break;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
log::warn!("WebSocket client lagged, skipped {} events", skipped);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle client messages
|
||||
socket.on("message", |socket: SocketRef, Data::<Value>(data)| {
|
||||
log::debug!("Received WebSocket message: {:?}", data);
|
||||
// Echo back the message for testing
|
||||
socket.emit("echo", &data).ok();
|
||||
});
|
||||
|
||||
// Handle client disconnect
|
||||
socket.on_disconnect(|socket: SocketRef| {
|
||||
log::info!("WebSocket client disconnected: {}", socket.id);
|
||||
});
|
||||
|
||||
log::info!("WebSocket client connected: {}", socket.id);
|
||||
});
|
||||
|
||||
layer
|
||||
}
|
||||
@@ -682,6 +682,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(v) => log::info!("Checked ffmpeg version: {v}"),
|
||||
}
|
||||
|
||||
http_server::start_api_server(state).await;
|
||||
http_server::api_server::start_api_server(state).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
invoke,
|
||||
TAURI_ENV,
|
||||
ENDPOINT,
|
||||
listen,
|
||||
onConnectionRestore,
|
||||
} from "../invoker";
|
||||
import { invoke, TAURI_ENV, ENDPOINT, listen } from "../invoker";
|
||||
import { Upload, X, CheckCircle } from "lucide-svelte";
|
||||
import { createEventDispatcher, onDestroy } from "svelte";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
@@ -120,11 +114,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 注册连接恢复回调
|
||||
if (!TAURI_ENV) {
|
||||
onConnectionRestore(checkTaskStatus);
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
progressUpdateListener?.then((fn) => fn());
|
||||
progressFinishedListener?.then((fn) => fn());
|
||||
|
||||
@@ -5,6 +5,7 @@ import { convertFileSrc as tauri_convert } from "@tauri-apps/api/core";
|
||||
import { listen as tauri_listen } from "@tauri-apps/api/event";
|
||||
import { open as tauri_open } from "@tauri-apps/plugin-shell";
|
||||
import { onOpenUrl as tauri_onOpenUrl } from "@tauri-apps/plugin-deep-link";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -171,60 +172,87 @@ async function get_cover(coverType: string, coverPath: string) {
|
||||
return `${ENDPOINT}/${coverType}/${coverPath}`;
|
||||
}
|
||||
|
||||
let event_source: EventSource | null = null;
|
||||
let reconnectTimeout: number | null = null;
|
||||
const MAX_RECONNECT_ATTEMPTS = 5;
|
||||
let reconnectAttempts = 0;
|
||||
let socket: Socket | null = null;
|
||||
|
||||
// 连接恢复回调列表
|
||||
const connectionRestoreCallbacks: Array<() => void> = [];
|
||||
// Socket.IO 事件监听器映射
|
||||
const eventListeners: Map<string, Array<(data: any) => void>> = new Map();
|
||||
|
||||
function createEventSource() {
|
||||
function createSocket() {
|
||||
if (TAURI_ENV) return;
|
||||
|
||||
if (event_source) {
|
||||
event_source.close();
|
||||
if (socket) {
|
||||
socket.disconnect();
|
||||
}
|
||||
event_source = new EventSource(`${ENDPOINT}/api/sse`);
|
||||
|
||||
event_source.onopen = () => {
|
||||
reconnectAttempts = 0;
|
||||
// 构建 Socket.IO URL
|
||||
console.log("endpoint:", ENDPOINT);
|
||||
const socketUrl = ENDPOINT;
|
||||
socket = io(`${socketUrl}/ws`, {
|
||||
transports: ["websocket", "polling"],
|
||||
autoConnect: true,
|
||||
reconnection: true,
|
||||
});
|
||||
|
||||
// 触发连接恢复回调
|
||||
connectionRestoreCallbacks.forEach((callback) => {
|
||||
try {
|
||||
callback();
|
||||
} catch (e) {
|
||||
console.error("[SSE] Connection restore callback error:", e);
|
||||
}
|
||||
});
|
||||
};
|
||||
socket.on("connect", () => {
|
||||
console.log("[Socket.IO] Connected to server");
|
||||
});
|
||||
|
||||
event_source.onerror = (error) => {
|
||||
// 只有在连接真正关闭时才进行重连
|
||||
if (
|
||||
event_source.readyState === EventSource.CLOSED &&
|
||||
reconnectAttempts < MAX_RECONNECT_ATTEMPTS
|
||||
) {
|
||||
reconnectAttempts++;
|
||||
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 10000);
|
||||
socket.on("disconnect", (reason) => {
|
||||
console.log("[Socket.IO] Disconnected from server:", reason);
|
||||
});
|
||||
|
||||
reconnectTimeout = window.setTimeout(() => {
|
||||
createEventSource();
|
||||
}, delay);
|
||||
} else {
|
||||
console.error("[SSE] Max reconnection attempts reached, giving up");
|
||||
socket.on("connect_error", (error) => {
|
||||
console.error("[Socket.IO] Connection error:", error);
|
||||
});
|
||||
|
||||
// 监听服务器发送的事件
|
||||
socket.on("progress", (data) => {
|
||||
const eventType = data.event || "message";
|
||||
|
||||
// 触发对应的事件监听器
|
||||
const listeners = eventListeners.get(eventType);
|
||||
if (listeners) {
|
||||
listeners.forEach((callback) => {
|
||||
try {
|
||||
callback({
|
||||
type: eventType,
|
||||
payload: data.data,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[Socket.IO] Event listener error for ${eventType}:`,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 注册连接恢复回调
|
||||
function onConnectionRestore(callback: () => void) {
|
||||
connectionRestoreCallbacks.push(callback);
|
||||
socket.on("danmu", (data) => {
|
||||
const eventType = data.event || "message";
|
||||
|
||||
// 触发对应的事件监听器
|
||||
const listeners = eventListeners.get(eventType);
|
||||
if (listeners) {
|
||||
listeners.forEach((callback) => {
|
||||
try {
|
||||
callback({
|
||||
type: eventType,
|
||||
payload: data.data,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[Socket.IO] Event listener error for ${eventType}:`,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!TAURI_ENV) {
|
||||
createEventSource();
|
||||
createSocket();
|
||||
}
|
||||
|
||||
async function listen<T>(event: string, callback: (data: any) => void) {
|
||||
@@ -232,13 +260,26 @@ async function listen<T>(event: string, callback: (data: any) => void) {
|
||||
return await tauri_listen(event, callback);
|
||||
}
|
||||
|
||||
event_source.addEventListener(event, (event_data) => {
|
||||
const data = JSON.parse(event_data.data);
|
||||
callback({
|
||||
type: event,
|
||||
payload: data,
|
||||
});
|
||||
});
|
||||
// 将事件监听器添加到映射中
|
||||
if (!eventListeners.has(event)) {
|
||||
eventListeners.set(event, []);
|
||||
}
|
||||
eventListeners.get(event)!.push(callback);
|
||||
|
||||
// 返回一个清理函数
|
||||
return () => {
|
||||
const listeners = eventListeners.get(event);
|
||||
if (listeners) {
|
||||
const index = listeners.indexOf(callback);
|
||||
if (index > -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
// 如果没有监听器了,删除这个事件
|
||||
if (listeners.length === 0) {
|
||||
eventListeners.delete(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function open(url: string) {
|
||||
@@ -273,6 +314,5 @@ export {
|
||||
log,
|
||||
close_window,
|
||||
onOpenUrl,
|
||||
onConnectionRestore,
|
||||
get_cover,
|
||||
};
|
||||
|
||||
56
yarn.lock
56
yarn.lock
@@ -886,6 +886,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224"
|
||||
integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
|
||||
|
||||
"@socket.io/component-emitter@~3.1.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
|
||||
integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
|
||||
|
||||
"@sveltejs/vite-plugin-svelte-inspector@^1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz#c99fcb73aaa845a3e2c0563409aeb3ee0b863add"
|
||||
@@ -2193,6 +2198,13 @@ debug@^4.3.4, debug@^4.4.0:
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@~4.3.1, debug@~4.3.2:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
||||
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
decamelize@1.2.0, decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
@@ -2279,6 +2291,22 @@ emoji-regex@^9.2.2:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||
|
||||
engine.io-client@~6.6.1:
|
||||
version "6.6.3"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.6.3.tgz#815393fa24f30b8e6afa8f77ccca2f28146be6de"
|
||||
integrity sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
engine.io-parser "~5.2.1"
|
||||
ws "~8.17.1"
|
||||
xmlhttprequest-ssl "~2.1.1"
|
||||
|
||||
engine.io-parser@~5.2.1:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f"
|
||||
integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==
|
||||
|
||||
entities@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
||||
@@ -3478,6 +3506,24 @@ simple-wcswidth@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz#66722f37629d5203f9b47c5477b1225b85d6525b"
|
||||
integrity sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==
|
||||
|
||||
socket.io-client@^4.8.1:
|
||||
version "4.8.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb"
|
||||
integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.2"
|
||||
engine.io-client "~6.6.1"
|
||||
socket.io-parser "~4.2.4"
|
||||
|
||||
socket.io-parser@~4.2.4:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
|
||||
integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
sorcery@^0.11.0:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.11.1.tgz#7cac27ae9c9549b3cd1e4bb85317f7b2dc7b7e22"
|
||||
@@ -4059,6 +4105,16 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
ws@~8.17.1:
|
||||
version "8.17.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
|
||||
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
|
||||
|
||||
xmlhttprequest-ssl@~2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23"
|
||||
integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||
|
||||
Reference in New Issue
Block a user