️ feat: Optimize websocket binding method, optimize download speed event current limit

This commit is contained in:
jarvis2f 2025-01-12 14:04:50 +08:00
parent 23c5758e0f
commit d3f81c9a14
9 changed files with 234 additions and 158 deletions

View file

@ -110,7 +110,7 @@ public class AutoDownloadVerticle extends AbstractVerticle {
}); });
vertx.eventBus().consumer(EventEnum.SETTING_UPDATE.address(SettingKey.autoDownloadLimit.name()), message -> { vertx.eventBus().consumer(EventEnum.SETTING_UPDATE.address(SettingKey.autoDownloadLimit.name()), message -> {
log.debug("Auto download limit update: %s".formatted(message.body())); log.debug("Auto download limit update: %s".formatted(message.body()));
this.limit = Convert.toInt(message.body()); this.limit = Convert.toInt(message.body(), DEFAULT_LIMIT);
}); });
vertx.eventBus().consumer(EventEnum.MESSAGE_RECEIVED.address(), message -> { vertx.eventBus().consumer(EventEnum.MESSAGE_RECEIVED.address(), message -> {
log.trace("Auto download message received: %s".formatted(message.body())); log.trace("Auto download message received: %s".formatted(message.body()));

View file

@ -23,6 +23,14 @@ public enum EventEnum {
* body = JSONObject with "telegramId", "chatId", "messageId" * body = JSONObject with "telegramId", "chatId", "messageId"
*/ */
MESSAGE_RECEIVED, MESSAGE_RECEIVED,
/**
* suffix = null <br>
* body = JSONObject with "telegramId", "payload"
*
* @see telegram.files.EventPayload
*/
TELEGRAM_EVENT,
; ;
public String address() { public String address() {

View file

@ -13,6 +13,7 @@ import io.vertx.core.http.CookieSameSite;
import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.ext.healthchecks.HealthCheckHandler; import io.vertx.ext.healthchecks.HealthCheckHandler;
@ -55,6 +56,7 @@ public class HttpVerticle extends AbstractVerticle {
initHttpServer() initHttpServer()
.compose(r -> initTelegramVerticles()) .compose(r -> initTelegramVerticles())
.compose(r -> initAutoDownloadVerticle()) .compose(r -> initAutoDownloadVerticle())
.compose(r -> initEventConsumer())
.onSuccess(startPromise::complete) .onSuccess(startPromise::complete)
.onFailure(startPromise::fail); .onFailure(startPromise::fail);
} }
@ -216,12 +218,37 @@ public class HttpVerticle extends AbstractVerticle {
.mapEmpty(); .mapEmpty();
} }
private Future<Void> initEventConsumer() {
vertx.eventBus().consumer(EventEnum.TELEGRAM_EVENT.address(), message -> {
log.debug("Received telegram event: %s".formatted(message.body()));
JsonObject jsonObject = (JsonObject) message.body();
String telegramId = jsonObject.getString("telegramId");
EventPayload payload = jsonObject.getJsonObject("payload").mapTo(EventPayload.class);
sessionTelegramVerticles.entrySet().stream()
.filter(e -> Objects.equals(Convert.toStr(e.getValue().getId()), telegramId))
.map(Map.Entry::getKey)
.forEach(sessionId -> {
String wsHandlerId = clients.get(sessionId);
if (StrUtil.isNotBlank(wsHandlerId)) {
vertx.eventBus().send(wsHandlerId, Json.encode(payload));
}
});
});
return Future.succeededFuture();
}
private void handleWebSocket(RoutingContext ctx) { private void handleWebSocket(RoutingContext ctx) {
String sessionId = ctx.session().id(); String sessionId = ctx.session().id();
String telegramId = ctx.request().getParam("telegramId");
ctx.request().toWebSocket() ctx.request().toWebSocket()
.onSuccess(ws -> { .onSuccess(ws -> {
log.debug("Upgraded to WebSocket. SessionId: %s".formatted(sessionId)); log.debug("Upgraded to WebSocket. SessionId: %s".formatted(sessionId));
clients.put(sessionId, ws.textHandlerID()); clients.put(sessionId, ws.textHandlerID());
if (StrUtil.isNotBlank(telegramId) && !handleTelegramChange(sessionId, telegramId)) {
log.debug("Failed to change telegram verticle. SessionId: %s".formatted(sessionId));
}
long timerId = vertx.setPeriodic(30000, id -> { long timerId = vertx.setPeriodic(30000, id -> {
if (!ws.isClosed()) { if (!ws.isClosed()) {
@ -237,9 +264,7 @@ public class HttpVerticle extends AbstractVerticle {
log.debug("WebSocket closed. SessionId: %s".formatted(sessionId)); log.debug("WebSocket closed. SessionId: %s".formatted(sessionId));
}); });
ws.textMessageHandler(text -> { ws.textMessageHandler(text -> log.debug("Received WebSocket message: " + text));
log.debug("Received WebSocket message: " + text);
});
}) })
.onFailure(err -> log.warn("Failed to upgrade to WebSocket: %s".formatted(err.getMessage()))); .onFailure(err -> log.warn("Failed to upgrade to WebSocket: %s".formatted(err.getMessage())));
} }
@ -303,7 +328,6 @@ public class HttpVerticle extends AbstractVerticle {
String proxyName = jsonObject.getString("proxyName"); String proxyName = jsonObject.getString("proxyName");
TelegramVerticle newTelegramVerticle = new TelegramVerticle(DataVerticle.telegramRepository.getRootPath()); TelegramVerticle newTelegramVerticle = new TelegramVerticle(DataVerticle.telegramRepository.getRootPath());
newTelegramVerticle.bindHttpSession(sessionId);
newTelegramVerticle.setProxy(proxyName); newTelegramVerticle.setProxy(proxyName);
sessionTelegramVerticles.put(sessionId, newTelegramVerticle); sessionTelegramVerticles.put(sessionId, newTelegramVerticle);
telegramVerticles.add(newTelegramVerticle); telegramVerticles.add(newTelegramVerticle);
@ -356,11 +380,10 @@ public class HttpVerticle extends AbstractVerticle {
String query = ctx.request().getParam("query"); String query = ctx.request().getParam("query");
String chatId = ctx.request().getParam("chatId"); String chatId = ctx.request().getParam("chatId");
getTelegramVerticle(telegramId) getTelegramVerticle(telegramId)
.ifPresentOrElse(telegramVerticle -> { .ifPresentOrElse(telegramVerticle ->
telegramVerticle.getChats(Convert.toLong(chatId), query) telegramVerticle.getChats(Convert.toLong(chatId), query)
.onSuccess(ctx::json) .onSuccess(ctx::json)
.onFailure(ctx::fail); .onFailure(ctx::fail), () -> ctx.fail(404));
}, () -> ctx.fail(404));
} }
private void handleTelegramFiles(RoutingContext ctx) { private void handleTelegramFiles(RoutingContext ctx) {
@ -427,16 +450,24 @@ public class HttpVerticle extends AbstractVerticle {
private void handleTelegramChange(RoutingContext ctx) { private void handleTelegramChange(RoutingContext ctx) {
String sessionId = ctx.session().id(); String sessionId = ctx.session().id();
String telegramId = ctx.request().getParam("telegramId"); String telegramId = ctx.request().getParam("telegramId");
if (handleTelegramChange(sessionId, telegramId)) {
ctx.end();
} else {
ctx.fail(400);
}
}
private boolean handleTelegramChange(String sessionId, String telegramId) {
if (StrUtil.isBlank(telegramId)) { if (StrUtil.isBlank(telegramId)) {
sessionTelegramVerticles.remove(sessionId); sessionTelegramVerticles.remove(sessionId);
ctx.end(); return true;
} }
getTelegramVerticle(telegramId) Optional<TelegramVerticle> optionalTelegramVerticle = getTelegramVerticle(telegramId);
.ifPresentOrElse(telegramVerticle -> { if (optionalTelegramVerticle.isEmpty()) {
telegramVerticle.bindHttpSession(sessionId); return false;
sessionTelegramVerticles.put(sessionId, telegramVerticle); }
ctx.end(); sessionTelegramVerticles.put(sessionId, optionalTelegramVerticle.get());
}, () -> ctx.fail(404)); return true;
} }
private void handleTelegramToggleProxy(RoutingContext ctx) { private void handleTelegramToggleProxy(RoutingContext ctx) {
@ -478,13 +509,11 @@ public class HttpVerticle extends AbstractVerticle {
ctx.fail(400); ctx.fail(400);
return; return;
} }
String sessionId = ctx.session().id();
TelegramVerticle telegramVerticle = getTelegramVerticle(ctx); TelegramVerticle telegramVerticle = getTelegramVerticle(ctx);
if (telegramVerticle == null) { if (telegramVerticle == null) {
return; return;
} }
JsonObject params = ctx.body().asJsonObject(); JsonObject params = ctx.body().asJsonObject();
telegramVerticle.bindHttpSession(sessionId);
telegramVerticle.execute(method, params == null ? null : params.getMap()) telegramVerticle.execute(method, params == null ? null : params.getMap())
.onSuccess(code -> ctx.json(JsonObject.of("code", code))) .onSuccess(code -> ctx.json(JsonObject.of("code", code)))
.onFailure(ctx::fail); .onFailure(ctx::fail);
@ -637,10 +666,6 @@ public class HttpVerticle extends AbstractVerticle {
.findFirst(); .findFirst();
} }
public static String getWSHandlerId(String sessionId) {
return clients.get(sessionId);
}
private TelegramVerticle getTelegramVerticle(RoutingContext ctx) { private TelegramVerticle getTelegramVerticle(RoutingContext ctx) {
String sessionId = ctx.session().id(); String sessionId = ctx.session().id();
TelegramVerticle telegramVerticle = sessionTelegramVerticles.get(sessionId); TelegramVerticle telegramVerticle = sessionTelegramVerticles.get(sessionId);

View file

@ -42,8 +42,6 @@ public class TelegramVerticle extends AbstractVerticle {
private Client client; private Client client;
private volatile String httpSessionId;
public boolean authorized = false; public boolean authorized = false;
public TdApi.AuthorizationState lastAuthorizationState; public TdApi.AuthorizationState lastAuthorizationState;
@ -62,6 +60,10 @@ public class TelegramVerticle extends AbstractVerticle {
private long avgSpeedPersistenceTimerId; private long avgSpeedPersistenceTimerId;
private long lastFileEventTime;
private long lastFileDownloadEventTime;
static { static {
Client.setLogMessageHandler(0, new LogMessageHandler()); Client.setLogMessageHandler(0, new LogMessageHandler());
@ -137,10 +139,6 @@ public class TelegramVerticle extends AbstractVerticle {
return true; return true;
} }
public void bindHttpSession(String httpSessionId) {
this.httpSessionId = httpSessionId;
}
public Future<JsonObject> getTelegramAccount() { public Future<JsonObject> getTelegramAccount() {
return Future.future(promise -> { return Future.future(promise -> {
if (!authorized) { if (!authorized) {
@ -441,11 +439,6 @@ public class TelegramVerticle extends AbstractVerticle {
return this.execute(new TdApi.ToggleDownloadIsPaused(fileId, isPaused)); return this.execute(new TdApi.ToggleDownloadIsPaused(fileId, isPaused));
}) })
.onSuccess(r ->
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE_STATUS, new JsonObject()
.put("fileId", fileId)
.put("downloadStatus", isPaused ? FileRecord.DownloadStatus.paused : FileRecord.DownloadStatus.downloading)
)))
.mapEmpty(); .mapEmpty();
} }
@ -644,14 +637,8 @@ public class TelegramVerticle extends AbstractVerticle {
} }
private void sendHttpEvent(EventPayload payload) { private void sendHttpEvent(EventPayload payload) {
if (this.httpSessionId == null) return; vertx.eventBus().send(EventEnum.TELEGRAM_EVENT.address(),
JsonObject.of("telegramId", this.getId(), "payload", JsonObject.mapFrom(payload)));
String address = HttpVerticle.getWSHandlerId(httpSessionId);
if (address == null) {
log.debug("[%s] Can not found websocket textHandlerID for session id:%s".formatted(getRootId(), httpSessionId));
return;
}
vertx.eventBus().send(address, Json.encode(payload));
} }
private void handleAuthorizationResult(TdApi.Object object) { private void handleAuthorizationResult(TdApi.Object object) {
@ -797,6 +784,7 @@ public class TelegramVerticle extends AbstractVerticle {
if (file != null) { if (file != null) {
String localPath = null; String localPath = null;
Long completionDate = null; Long completionDate = null;
long downloadedSize = file.local == null ? 0 : file.local.downloadedSize;
if (file.local != null && file.local.isDownloadingCompleted) { if (file.local != null && file.local.isDownloadingCompleted) {
localPath = file.local.path; localPath = file.local.path;
completionDate = System.currentTimeMillis(); completionDate = System.currentTimeMillis();
@ -814,16 +802,23 @@ public class TelegramVerticle extends AbstractVerticle {
.put("downloadStatus", r.getString("downloadStatus")) .put("downloadStatus", r.getString("downloadStatus"))
.put("localPath", r.getString("localPath")) .put("localPath", r.getString("localPath"))
.put("completionDate", r.getLong("completionDate")) .put("completionDate", r.getLong("completionDate"))
.put("downloadedSize", downloadedSize)
)); ));
}); });
} }
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE, updateFile)); if (lastFileEventTime == 0 || System.currentTimeMillis() - lastFileEventTime > 1000) {
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE, updateFile));
lastFileEventTime = System.currentTimeMillis();
}
} }
private void onFileDownloadsUpdated(TdApi.UpdateFileDownloads updateFileDownloads) { private void onFileDownloadsUpdated(TdApi.UpdateFileDownloads updateFileDownloads) {
log.trace("[%s] Receive file downloads update: %s".formatted(getRootId(), updateFileDownloads)); log.trace("[%s] Receive file downloads update: %s".formatted(getRootId(), updateFileDownloads));
avgSpeed.update(updateFileDownloads.downloadedSize, System.currentTimeMillis()); avgSpeed.update(updateFileDownloads.downloadedSize, System.currentTimeMillis());
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE_DOWNLOAD, updateFileDownloads)); if (lastFileDownloadEventTime == 0 || System.currentTimeMillis() - lastFileDownloadEventTime > 1000) {
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE_DOWNLOAD, updateFileDownloads));
lastFileDownloadEventTime = System.currentTimeMillis();
}
} }
private void onMessageReceived(TdApi.Message message) { private void onMessageReceived(TdApi.Message message) {

View file

@ -171,8 +171,8 @@ export default function AccountCreator({
if (!authState && !isMethodExecuting) { if (!authState && !isMethodExecuting) {
return ( return (
<div className="flex items-center justify-center space-x-2 text-xl"> <div className="flex flex-col items-center justify-center space-y-2 rounded bg-gray-50 p-2">
<p>Waiting for the telegram account to be initialized, please wait</p> <p>Waiting for the telegram account to be initialized, please wait.</p>
<p>If it takes too long, please refresh the page or try again later.</p> <p>If it takes too long, please refresh the page or try again later.</p>
<Ellipsis className="h-4 w-4 animate-pulse" /> <Ellipsis className="h-4 w-4 animate-pulse" />
</div> </div>

View file

@ -25,110 +25,141 @@ interface FileExtraProps {
rowHeight: RowHeight; rowHeight: RowHeight;
} }
export default function FileExtra({ file, rowHeight }: FileExtraProps) { function FileName({ file }: { file: TelegramFile }) {
if (!file.fileName) {
return null;
}
return (
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}>
<p className="flex items-center gap-2">
<Mountain className="h-4 w-4" />
<span className="rounded px-1 text-sm hover:bg-gray-100">
{file.fileName}
</span>
</p>
</SpoiledWrapper>
);
}
function FileCaption({ file, rowHeight }: FileExtraProps) {
if (!file.caption) {
return null;
}
return (
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}>
<Tooltip>
<TooltipTrigger>
<div className="flex items-center gap-2">
<Captions className="h-4 w-4 flex-shrink-0" />
<p
className={cn(
rowHeight !== "l" && "line-clamp-1",
"overflow-hidden truncate text-ellipsis text-wrap text-start text-sm",
)}
>
{file.caption}
</p>
</div>
</TooltipTrigger>
<TooltipContent>
<p
className="max-w-80 text-wrap rounded p-2"
dangerouslySetInnerHTML={{
__html: file.caption.replaceAll("\n", "<br />"),
}}
></p>
</TooltipContent>
</Tooltip>
</SpoiledWrapper>
);
}
function FilePath({ file }: { file: TelegramFile }) {
const [, copyToClipboard] = useCopyToClipboard(); const [, copyToClipboard] = useCopyToClipboard();
if (!file.localPath) {
return null;
}
return (
<Tooltip>
<TooltipTrigger>
<div className="flex items-center gap-2 text-sm">
<FileCheck className="h-4 w-4 flex-shrink-0" />
<p
className="group line-clamp-1 cursor-pointer overflow-hidden truncate text-ellipsis text-wrap rounded px-1 hover:bg-gray-100"
onClick={() => copyToClipboard(file.localPath)}
>
{file.localPath.split("/").pop()}
<Copy className="ml-1 inline-flex h-4 w-4 opacity-0 group-hover:opacity-100" />
</p>
</div>
</TooltipTrigger>
<TooltipContent>
<div className="max-w-80 text-wrap rounded p-2">{file.localPath}</div>
</TooltipContent>
</Tooltip>
);
}
function FileTime({ file }: { file: TelegramFile }) {
return (
<div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<p className="flex items-center gap-2">
<Clock className="h-4 w-4" />
<span className="rounded px-1 text-sm text-muted-foreground hover:bg-gray-100">
{formatDistanceToNow(new Date(file.date * 1000), {
addSuffix: true,
})}
</span>
</p>
</TooltipTrigger>
<TooltipContent>
<div className="max-w-80 text-wrap rounded p-2">
{`Message received at ${new Date(file.date * 1000).toLocaleString()}`}
</div>
</TooltipContent>
</Tooltip>
{file.completionDate && (
<Tooltip>
<TooltipTrigger asChild>
<p className="flex items-center gap-2">
<ClockArrowDown className="h-4 w-4" />
<span className="rounded px-1 text-sm text-muted-foreground hover:bg-gray-100">
{formatDistanceToNow(new Date(file.completionDate), {
addSuffix: true,
})}
</span>
</p>
</TooltipTrigger>
<TooltipContent>
<div className="max-w-80 text-wrap rounded p-2">
{`File downloaded at ${new Date(file.completionDate).toLocaleString()}`}
</div>
</TooltipContent>
</Tooltip>
)}
</div>
);
}
export default function FileExtra({ file, rowHeight }: FileExtraProps) {
if (rowHeight === "s") {
return (
<TooltipProvider>
{file.fileName ? <FileName file={file} /> : <FileTime file={file} />}
</TooltipProvider>
);
}
return ( return (
<div className="relative flex flex-col space-y-1 overflow-hidden"> <div className="relative flex flex-col space-y-1 overflow-hidden">
<TooltipProvider> <TooltipProvider>
{file.fileName && ( <FileName file={file} />
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}> <FileCaption file={file} rowHeight={rowHeight} />
<p className="flex items-center gap-2"> <FilePath file={file} />
<Mountain className="h-4 w-4" /> <FileTime file={file} />
<span className="rounded px-1 text-sm hover:bg-gray-100">
{file.fileName}
</span>
</p>
</SpoiledWrapper>
)}
{rowHeight !== "s" && file.caption && (
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}>
<Tooltip>
<TooltipTrigger>
<div className="flex items-center gap-2">
<Captions className="h-4 w-4 flex-shrink-0" />
<p
className={cn(
rowHeight !== "l" && "line-clamp-1",
"overflow-hidden truncate text-ellipsis text-wrap text-start text-sm",
)}
>
{file.caption}
</p>
</div>
</TooltipTrigger>
<TooltipContent>
<p
className="max-w-80 text-wrap rounded p-2"
dangerouslySetInnerHTML={{
__html: file.caption.replaceAll("\n", "<br />"),
}}
></p>
</TooltipContent>
</Tooltip>
</SpoiledWrapper>
)}
{rowHeight !== "s" && file.localPath && (
<Tooltip>
<TooltipTrigger>
<div className="flex items-center gap-2 text-sm">
<FileCheck className="h-4 w-4 flex-shrink-0" />
<p
className="group line-clamp-1 cursor-pointer overflow-hidden truncate text-ellipsis text-wrap rounded px-1 hover:bg-gray-100"
onClick={() => copyToClipboard(file.localPath)}
>
{file.localPath.split("/").pop()}
<Copy className="ml-1 inline-flex h-4 w-4 opacity-0 group-hover:opacity-100" />
</p>
</div>
</TooltipTrigger>
<TooltipContent>
<div className="max-w-80 text-wrap rounded p-2">
{file.localPath}
</div>
</TooltipContent>
</Tooltip>
)}
{rowHeight !== "s" && (
<div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<p className="flex items-center gap-2">
<Clock className="h-4 w-4" />
<span className="rounded px-1 text-sm text-muted-foreground hover:bg-gray-100">
{formatDistanceToNow(new Date(file.date * 1000), {
addSuffix: true,
})}
</span>
</p>
</TooltipTrigger>
<TooltipContent>
<div className="max-w-80 text-wrap rounded p-2">
{`Message received at ${new Date(file.date * 1000).toLocaleString()}`}
</div>
</TooltipContent>
</Tooltip>
{file.completionDate && (
<Tooltip>
<TooltipTrigger asChild>
<p className="flex items-center gap-2">
<ClockArrowDown className="h-4 w-4" />
<span className="rounded px-1 text-sm text-muted-foreground hover:bg-gray-100">
{formatDistanceToNow(new Date(file.completionDate), {
addSuffix: true,
})}
</span>
</p>
</TooltipTrigger>
<TooltipContent>
<div className="max-w-80 text-wrap rounded p-2">
{`File downloaded at ${new Date(file.completionDate).toLocaleString()}`}
</div>
</TooltipContent>
</Tooltip>
)}
</div>
)}
</TooltipProvider> </TooltipProvider>
</div> </div>
); );

View file

@ -14,9 +14,14 @@ export function useFileSpeed(fileId: number) {
lastTimestamp: 0, lastTimestamp: 0,
}); });
const [debounceSpeed] = useDebounce(downloadSpeed.speed, 1000, { const [debounceSpeed] = useDebounce(downloadSpeed.speed, 300, {
leading: true, leading: true,
maxWait: 2000, maxWait: 1000,
});
const [debounceProgress] = useDebounce(downloadProgress, 300, {
leading: true,
maxWait: 1000,
}); });
useEffect(() => { useEffect(() => {
@ -94,7 +99,7 @@ export function useFileSpeed(fileId: number) {
}, []); }, []);
return { return {
downloadProgress, downloadProgress: debounceProgress,
downloadSpeed: debounceSpeed, downloadSpeed: debounceSpeed,
}; };
} }

View file

@ -8,7 +8,7 @@ import useSWRInfinite from "swr/infinite";
import { useWebsocket } from "@/hooks/use-websocket"; import { useWebsocket } from "@/hooks/use-websocket";
import { WebSocketMessageType } from "@/lib/websocket-types"; import { WebSocketMessageType } from "@/lib/websocket-types";
import useLocalStorage from "@/hooks/use-local-storage"; import useLocalStorage from "@/hooks/use-local-storage";
import {useDebounce} from "use-debounce"; import { useDebounce } from "use-debounce";
const DEFAULT_FILTERS: FileFilter = { const DEFAULT_FILTERS: FileFilter = {
search: "", search: "",
@ -31,6 +31,7 @@ export function useFiles(accountId: string, chatId: string) {
downloadStatus: DownloadStatus; downloadStatus: DownloadStatus;
localPath: string; localPath: string;
completionDate: number; completionDate: number;
downloadedSize: number;
} }
> >
>({}); >({});
@ -76,6 +77,7 @@ export function useFiles(accountId: string, chatId: string) {
downloadStatus: DownloadStatus; downloadStatus: DownloadStatus;
localPath: string; localPath: string;
completionDate: number; completionDate: number;
downloadedSize: number;
}; };
setLatestFileStatus((prev) => ({ setLatestFileStatus((prev) => ({
@ -84,7 +86,10 @@ export function useFiles(accountId: string, chatId: string) {
downloadStatus: downloadStatus:
data.downloadStatus ?? prev[data.fileId]?.downloadStatus, data.downloadStatus ?? prev[data.fileId]?.downloadStatus,
localPath: data.localPath ?? prev[data.fileId]?.localPath, localPath: data.localPath ?? prev[data.fileId]?.localPath,
completionDate: data.completionDate ?? prev[data.fileId]?.completionDate, completionDate:
data.completionDate ?? prev[data.fileId]?.completionDate,
downloadedSize:
data.downloadedSize ?? prev[data.fileId]?.downloadedSize,
}, },
})); }));
}, [lastJsonMessage]); }, [lastJsonMessage]);
@ -101,6 +106,8 @@ export function useFiles(accountId: string, chatId: string) {
localPath: latestFilesStatus[file.id]?.localPath ?? file.localPath, localPath: latestFilesStatus[file.id]?.localPath ?? file.localPath,
completionDate: completionDate:
latestFilesStatus[file.id]?.completionDate ?? file.completionDate, latestFilesStatus[file.id]?.completionDate ?? file.completionDate,
downloadedSize:
latestFilesStatus[file.id]?.downloadedSize ?? file.downloadedSize,
}); });
}); });
}); });

View file

@ -17,6 +17,7 @@ import {
import { useToast } from "./use-toast"; import { useToast } from "./use-toast";
import { useDebounce } from "use-debounce"; import { useDebounce } from "use-debounce";
import { getWsUrl } from "@/lib/api"; import { getWsUrl } from "@/lib/api";
import { useParams } from "next/navigation";
const WS_URL = `${getWsUrl()}`; const WS_URL = `${getWsUrl()}`;
@ -39,6 +40,7 @@ interface WebSocketProviderProps {
export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({ export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
children, children,
}) => { }) => {
const params = useParams();
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const [accountDownloadSpeed, setAccountDownloadSpeed] = useState({ const [accountDownloadSpeed, setAccountDownloadSpeed] = useState({
speed: 0, speed: 0,
@ -46,17 +48,20 @@ export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
lastTimestamp: 0, lastTimestamp: 0,
}); });
const { toast } = useToast(); const { toast } = useToast();
const [debounceSpeed] = useDebounce(accountDownloadSpeed.speed, 1000, { const [debounceSpeed] = useDebounce(accountDownloadSpeed.speed, 300, {
leading: true, leading: true,
maxWait: 2000, maxWait: 1000,
}); });
const { sendMessage, lastJsonMessage, readyState } = const { sendMessage, lastJsonMessage, readyState } =
useWebSocket<WebSocketMessage>(WS_URL, { useWebSocket<WebSocketMessage>(
shouldReconnect: (closeEvent) => true, `${WS_URL}?telegramId=${(params.accountId as string) ?? ""}`,
reconnectAttempts: 3, {
reconnectInterval: 3000, shouldReconnect: (closeEvent) => true,
}); reconnectAttempts: 3,
reconnectInterval: 3000,
},
);
const connectionStatus = { const connectionStatus = {
[ReadyState.CONNECTING]: "Connecting", [ReadyState.CONNECTING]: "Connecting",
@ -106,7 +111,7 @@ export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
lastDownloadedSize: downloadedSize, lastDownloadedSize: downloadedSize,
}; };
const timeDiff = (timestamp - prev.lastTimestamp) / 1000; const timeDiff = (timestamp - prev.lastTimestamp) / 1000;
if (prev.lastTimestamp === 0 || timeDiff <= 0) { if (prev.lastTimestamp === 0 || timeDiff <= 0 || downloadedSize <= prev.lastDownloadedSize) {
return { return {
...state, ...state,
speed: prev.speed, speed: prev.speed,