refactor: streaming

This commit is contained in:
Tristan Yang
2022-12-11 17:39:31 +08:00
parent dac1665994
commit ad8c05e8cc
+53 -39
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState, useCallback, useRef } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import BASE_URL from "../../../app/config"; import BASE_URL, { KEY_EXPIRE, KEY_REFRESH_TOKEN, KEY_TOKEN } from "../../../app/config";
import { setReady } from "../../../app/slices/ui"; import { setReady } from "../../../app/slices/ui";
import { import {
fillChannels, fillChannels,
@@ -33,44 +33,59 @@ const getQueryString = (params: { [key: string]: string }) => {
}); });
return sp.toString(); return sp.toString();
}; };
let inter: number | null = null; const getLocalAuthData = () => {
return {
token: localStorage.getItem(KEY_TOKEN) || "",
refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN) || "",
expireTime: Number(localStorage.getItem(KEY_EXPIRE) || +new Date())
};
};
let SSE: EventSource | undefined = undefined; let SSE: EventSource | undefined = undefined;
let opened = false; let connectionIsOpen = false;
let aliveInter: number = 0;
export default function useStreaming() { export default function useStreaming() {
const opened = useRef(false);
const [renewToken] = useRenewMutation(); const [renewToken] = useRenewMutation();
const [readyPullData, setReadyPullData] = useState(false); const [streamingReady, setStreamingReady] = useState(false);
const { const {
authData, authData: { user },
ui: { ready }, ui: { ready },
footprint: { afterMid, usersVersion, readUsers, readChannels } footprint: { afterMid, usersVersion, readUsers, readChannels }
} = useAppSelector((store) => store); } = useAppSelector((store) => store);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const loginUid = authData.user?.uid || 0; const loginUid = user?.uid || 0;
let aliveInter: number = 0;
const keepAlive = () => { const keepAlive = (timeout?: number) => {
clearTimeout(aliveInter); window.clearTimeout(aliveInter);
// 比15秒多2 // 比15秒多5
aliveInter = window.setTimeout(() => { aliveInter = window.setTimeout(() => {
// 重启连接 // 重启连接
stopStreaming(); stopStreaming();
startStreaming(); startStreaming();
}, 17000); }, timeout ?? 20000);
}; };
const startStreaming = async () => { const startStreaming = useCallback(
async () => {
console.log("start streaming", SSE, SSE?.readyState); console.log("start streaming", SSE, SSE?.readyState);
if (connectionIsOpen) return;
window.clearTimeout(aliveInter);
if (SSE && (SSE.readyState === EventSource.OPEN || SSE.readyState === EventSource.CONNECTING)) return; if (SSE && (SSE.readyState === EventSource.OPEN || SSE.readyState === EventSource.CONNECTING)) return;
const { token = "", refreshToken, expireTime = +new Date() } = authData; const { token, refreshToken, expireTime } = getLocalAuthData();
// token 非空 // token 非空
if (!token) { if (!token) {
console.info("sse start streaming no token", token); console.info("sse start streaming no token", token);
return; return;
} }
let _token = token;
// 如果token快要过期,先renew // 如果token快要过期,先renew
if (dayjs().isAfter(new Date(expireTime - 20 * 1000))) { if (dayjs().isAfter(new Date(expireTime - 20 * 1000))) {
renewToken({ token, refresh_token: refreshToken }); const resp = await renewToken({ token, refresh_token: refreshToken });
if ("error" in resp) {
return; return;
} else {
_token = resp.data.token;
}
// return;
} }
// 开始初始化 // 开始初始化
const params: { const params: {
@@ -78,7 +93,7 @@ export default function useStreaming() {
after_mid?: string; after_mid?: string;
users_version?: string; users_version?: string;
} = { } = {
"api-key": token, "api-key": _token,
}; };
// 如果afterMid是0,则不传该参数 // 如果afterMid是0,则不传该参数
if (afterMid !== 0) { if (afterMid !== 0) {
@@ -93,22 +108,19 @@ export default function useStreaming() {
SSE.onopen = () => { SSE.onopen = () => {
//todo //todo
opened = true; opened.current = true;
connectionIsOpen = true;
}; };
SSE.onerror = (err) => { SSE.onerror = (err) => {
const { readyState } = err.target as EventSource; const { readyState } = err.target as EventSource;
console.error("sse error", readyState, err); console.error("sse error", readyState, err);
// 连接还处于开启状态 // 连接还处于开启状态
if (readyState === EventSource.OPEN) { if (readyState === EventSource.OPEN || readyState === EventSource.CONNECTING) {
return; return;
} }
if (inter) { // 重连
clearTimeout(inter); connectionIsOpen = false;
} keepAlive(2000);
// // 重连
inter = window.setTimeout(() => {
startStreaming();
}, 1000);
}; };
SSE.onmessage = (evt) => { SSE.onmessage = (evt) => {
console.info("sse message", evt.data); console.info("sse message", evt.data);
@@ -122,6 +134,8 @@ export default function useStreaming() {
break; break;
case "ready": case "ready":
console.info("sse streaming ready"); console.info("sse streaming ready");
// 有时候,heartbeat不会发?
keepAlive();
dispatch(setReady()); dispatch(setReady());
break; break;
case "users_snapshot": case "users_snapshot":
@@ -287,33 +301,33 @@ export default function useStreaming() {
break; break;
} }
}; };
}; },
[afterMid, usersVersion],
);
const stopStreaming = () => { const stopStreaming = () => {
console.info("sse stop streaming"); console.info("sse stop streaming");
if (SSE) { if (SSE) {
SSE.close(); SSE.close();
SSE = undefined; SSE = undefined;
connectionIsOpen = false;
} }
}; };
const setStreamingReady = (ready: boolean) => {
setReadyPullData(ready);
};
useEffect(() => { useEffect(() => {
if (readyPullData) { // 确保只执行一次
// if (online) { const hasOpened = opened.current;
if (streamingReady && !hasOpened) {
startStreaming(); startStreaming();
// } else {
// stopStreaming();
// }
} }
return () => { return () => {
console.log("stop from readyPullData"); if (streamingReady && !hasOpened) {
console.log("stop from streamingReady");
stopStreaming(); stopStreaming();
}
}; };
}, [readyPullData]); }, [streamingReady]);
return { return {