fix: websocket

This commit is contained in:
naiba
2024-12-02 23:51:24 +08:00
parent df6c23af89
commit 36472035e1
2 changed files with 139 additions and 113 deletions

View File

@@ -63,6 +63,15 @@ const arraysEqual = (a: Uint8Array, b: Uint8Array) => {
const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl, ...props }) => { const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const fmRef = useRef<HTMLDivElement>(null); const fmRef = useRef<HTMLDivElement>(null);
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
return () => {
if (wsRef.current) {
wsRef.current.close();
}
};
}, []);
const [dOpen, setdOpen] = useState(false); const [dOpen, setdOpen] = useState(false);
const [uOpen, setuOpen] = useState(false); const [uOpen, setuOpen] = useState(false);
@@ -146,91 +155,96 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
worker.onmessage = async (event: MessageEvent<FMWorkerData>) => { worker.onmessage = async (event: MessageEvent<FMWorkerData>) => {
switch (event.data.type) { switch (event.data.type) {
case FMWorkerOpcode.Error: { case FMWorkerOpcode.Error: {
console.error('Error from worker', event.data.error); console.error('Error from worker', event.data.error);
break; break;
}
case FMWorkerOpcode.Progress: {
handleReady.current = true;
break;
}
case FMWorkerOpcode.Result: {
handleReady.current = false;
if (event.data.blob && event.data.fileName) {
const url = URL.createObjectURL(event.data.blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = event.data.fileName;
anchor.click();
URL.revokeObjectURL(url);
} }
case FMWorkerOpcode.Progress: {
handleReady.current = true;
break;
}
case FMWorkerOpcode.Result: {
handleReady.current = false;
firstChunk.current = true; if (event.data.blob && event.data.fileName) {
if (dOpen) setdOpen(false); const url = URL.createObjectURL(event.data.blob);
break; const anchor = document.createElement('a');
} anchor.href = url;
anchor.download = event.data.fileName;
anchor.click();
URL.revokeObjectURL(url);
}
firstChunk.current = true;
if (dOpen) setdOpen(false);
break;
}
} }
} }
const [currentPath, setPath] = useState(''); const [currentPath, setPath] = useState('');
useEffect(() => { useEffect(() => {
listFile(); if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
}, [currentPath]) listFile();
}
}, [wsRef.current, currentPath])
const ws = new WebSocket(wsUrl); useEffect(() => {
ws.binaryType = 'arraybuffer'; const ws = new WebSocket(wsUrl);
ws.onopen = () => { wsRef.current = ws;
listFile(); ws.binaryType = 'arraybuffer';
} ws.onopen = () => {
ws.onclose = (e) => { listFile();
console.log('WebSocket connection closed:', e); }
} ws.onclose = (e) => {
ws.onerror = (e) => { console.log('WebSocket connection closed:', e);
console.error(e); }
toast("Websocket" + " " + t("Error"), { ws.onerror = (e) => {
description: t("Results.UnExpectedError"), console.error(e);
}) toast("Websocket" + " " + t("Error"), {
}
ws.onmessage = async (e) => {
try {
const buf: ArrayBufferLike = e.data;
if (firstChunk.current) {
const identifier = new Uint8Array(buf, 0, 4);
if (arraysEqual(identifier, FMIdentifier.file)) {
worker.postMessage({ operation: 1, arrayBuffer: buf, fileName: currentBasename.current });
firstChunk.current = false;
} else if (arraysEqual(identifier, FMIdentifier.fileName)) {
const { path, fmList } = await fm.parseFMList(buf);
setPath(path);
setFMEntries(fmList);
} else if (arraysEqual(identifier, FMIdentifier.error)) {
const errBytes = buf.slice(4);
const errMsg = new TextDecoder('utf-8').decode(errBytes);
throw new Error(errMsg);
} else if (arraysEqual(identifier, FMIdentifier.complete)) {
// Upload completed
if (uOpen) setuOpen(false);
listFile();
} else {
throw new Error(t("Results.UnknownIdentifier"));
}
} else {
await waitForHandleReady();
worker.postMessage({ operation: 2, arrayBuffer: buf, fileName: currentBasename.current });
}
} catch (error) {
console.error('Error processing received data:', error);
toast("FM" + " " + t("Error"), {
description: t("Results.UnExpectedError"), description: t("Results.UnExpectedError"),
}) })
if (dOpen) setdOpen(false);
if (uOpen) setuOpen(false);
} }
} ws.onmessage = async (e) => {
try {
const buf: ArrayBufferLike = e.data;
const listFile = () => { if (firstChunk.current) {
const identifier = new Uint8Array(buf, 0, 4);
if (arraysEqual(identifier, FMIdentifier.file)) {
worker.postMessage({ operation: 1, arrayBuffer: buf, fileName: currentBasename.current });
firstChunk.current = false;
} else if (arraysEqual(identifier, FMIdentifier.fileName)) {
const { path, fmList } = await fm.parseFMList(buf);
setPath(path);
setFMEntries(fmList);
} else if (arraysEqual(identifier, FMIdentifier.error)) {
const errBytes = buf.slice(4);
const errMsg = new TextDecoder('utf-8').decode(errBytes);
throw new Error(errMsg);
} else if (arraysEqual(identifier, FMIdentifier.complete)) {
// Upload completed
if (uOpen) setuOpen(false);
listFile();
} else {
throw new Error(t("Results.UnknownIdentifier"));
}
} else {
await waitForHandleReady();
worker.postMessage({ operation: 2, arrayBuffer: buf, fileName: currentBasename.current });
}
} catch (error) {
console.error('Error processing received data:', error);
toast("FM" + " " + t("Error"), {
description: t("Results.UnExpectedError"),
})
if (dOpen) setdOpen(false);
if (uOpen) setuOpen(false);
}
}
}, [wsUrl])
let listFile = () => {
const prefix = new Int8Array([FMOpcode.List]); const prefix = new Int8Array([FMOpcode.List]);
const pathMsg = new TextEncoder().encode(currentPath); const pathMsg = new TextEncoder().encode(currentPath);
@@ -238,7 +252,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
msg.set(prefix); msg.set(prefix);
msg.set(pathMsg, prefix.length); msg.set(pathMsg, prefix.length);
ws.send(msg); wsRef.current?.send(msg);
} }
const downloadFile = (basename: string) => { const downloadFile = (basename: string) => {
@@ -250,7 +264,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
msg.set(prefix); msg.set(prefix);
msg.set(filePathMessage, prefix.length); msg.set(filePathMessage, prefix.length);
ws.send(msg); wsRef.current?.send(msg);
} }
const uploadFile = async (file: File) => { const uploadFile = async (file: File) => {
@@ -259,13 +273,13 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
// Send header // Send header
const header = fm.buildUploadHeader({ path: currentPath, file: file }); const header = fm.buildUploadHeader({ path: currentPath, file: file });
ws.send(header); wsRef.current?.send(header);
// Send data chunks // Send data chunks
while (offset < file.size) { while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize); const chunk = file.slice(offset, offset + chunkSize);
const arrayBuffer = await fm.readFileAsArrayBuffer(chunk); const arrayBuffer = await fm.readFileAsArrayBuffer(chunk);
if (arrayBuffer) ws.send(arrayBuffer); if (arrayBuffer) wsRef.current?.send(arrayBuffer);
offset += chunkSize; offset += chunkSize;
} }
} }

View File

@@ -28,22 +28,34 @@ interface XtermProps {
const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({ wsUrl, setClose, ...props }) => { const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({ wsUrl, setClose, ...props }) => {
const terminalRef = useRef<HTMLDivElement>(null); const terminalRef = useRef<HTMLDivElement>(null);
const wsRef = useRef<WebSocket | null>(null);
const ws = new WebSocket(wsUrl); useEffect(() => {
ws.binaryType = "arraybuffer"; return () => {
ws.onopen = () => { if (wsRef.current) {
onResize(); wsRef.current.close();
} }
ws.onclose = () => { };
terminal.dispose(); }, []);
setClose(true);
} useEffect(() => {
ws.onerror = (e) => { const ws = new WebSocket(wsUrl);
console.error(e); wsRef.current = ws;
toast("Websocket error", { ws.binaryType = "arraybuffer";
description: "View console for details.", ws.onopen = () => {
}) onResize();
} }
ws.onclose = () => {
terminal.dispose();
setClose(true);
}
ws.onerror = (e) => {
console.error(e);
toast("Websocket error", {
description: "View console for details.",
})
}
}, [wsUrl]);
const terminal = useRef( const terminal = useRef(
new Terminal({ new Terminal({
@@ -73,7 +85,7 @@ const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({ w
msg.set(prefix); msg.set(prefix);
msg.set(resizeMessage, prefix.length); msg.set(resizeMessage, prefix.length);
ws.send(msg); wsRef.current?.send(msg);
} }
}; };
@@ -92,20 +104,19 @@ const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({ w
}; };
useEffect(() => { useEffect(() => {
if (!ws || !terminalRef.current) return; if (!wsRef.current || !terminalRef.current) return;
const attachAddon = new AttachAddon(wsRef.current);
const attachAddon = new AttachAddon(ws);
terminal.loadAddon(attachAddon); terminal.loadAddon(attachAddon);
terminal.loadAddon(fitAddon); terminal.loadAddon(fitAddon);
terminal.open(terminalRef.current); terminal.open(terminalRef.current);
window.addEventListener('resize', onResize); window.addEventListener('resize', onResize);
return () => { return () => {
window.removeEventListener('resize', onResize); window.removeEventListener('resize', onResize);
if (ws) { if (wsRef.current) {
ws.close(); wsRef.current.close();
} }
}; };
}, [ws, terminal]); }, [wsRef.current, terminal]);
return <div ref={terminalRef} {...props} />; return <div ref={terminalRef} {...props} />;
}; };
@@ -116,21 +127,22 @@ export const TerminalPage = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
useEffect(() => { const fetchTerminal = async () => {
const fetchTerminal = async () => { if (id && !terminal) {
if (id && !terminal) { try {
try { const createdTerminal = await createTerminal(Number(id));
const createdTerminal = await createTerminal(Number(id)); setTerminal(createdTerminal);
setTerminal(createdTerminal); } catch (e) {
} catch (e) { toast("Terminal API Error", {
toast("Terminal API Error", { description: "View console for details.",
description: "View console for details.", })
}) console.error("fetch error", e);
console.error("fetch error", e); return;
return;
}
} }
}; }
}
useEffect(() => {
fetchTerminal(); fetchTerminal();
}, [id]); }, [id]);