release: v4.8.8 file transfer consent fix
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled

Complete the mandatory receiver-consent gate that was wired in the
backend but never connected to the UI callback chain:

- Add the missing onIncomingFileRequest (4th) callback to
  setFileTransferCallbacks in app.jsx — its absence caused
  handleFileTransferStart to auto-reject every incoming file.
- Remove independent callback registration from FileTransferComponent;
  the component was overwriting app-level callbacks on mount and
  nulling all four on unmount, silently breaking progress/received/
  error handlers whenever the panel was hidden.
- Lift pendingIncomingFiles state to the root component so consent
  prompts are shown regardless of panel visibility; auto-open the
  panel on incoming request.
- Add getReceivedFileObjectURL / revokeReceivedFileObjectURL on
  EnhancedSecureWebRTCManager for download buttons in the panel.
- Update file-transfer-ui-cleanup regression test to match the new
  single-owner callback architecture.
- All 14 tests pass; clean production build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
lockbitchat
2026-05-26 22:40:36 -04:00
parent 2468cb495e
commit 498d9a98e9
12 changed files with 200 additions and 209 deletions
Vendored
+34 -3
View File
@@ -1186,10 +1186,17 @@ var EnhancedChatInterface = ({
isVerified,
chatMessagesRef,
scrollToBottom,
webrtcManager
webrtcManager,
pendingIncomingFiles = [],
onIncomingDecision
}) => {
const [showScrollButton, setShowScrollButton] = React.useState(false);
const [showFileTransfer, setShowFileTransfer] = React.useState(false);
React.useEffect(() => {
if (pendingIncomingFiles.length > 0) {
setShowFileTransfer(true);
}
}, [pendingIncomingFiles.length]);
React.useEffect(() => {
if (chatMessagesRef.current && messages.length > 0) {
const { scrollTop, scrollHeight, clientHeight } = chatMessagesRef.current;
@@ -1398,7 +1405,9 @@ var EnhancedChatInterface = ({
className: "p-4 text-center text-red-400"
}, "FileTransferComponent not loaded")), {
webrtcManager,
isConnected: isFileTransferReady()
isConnected: isFileTransferReady(),
pendingIncomingFiles,
onIncomingDecision
})
]
)
@@ -1494,6 +1503,7 @@ var EnhancedSecureP2PChat = () => {
const [remoteVerificationConfirmed, setRemoteVerificationConfirmed] = React.useState(false);
const [bothVerificationsConfirmed, setBothVerificationsConfirmed] = React.useState(false);
const [pendingSession, setPendingSession] = React.useState(null);
const [pendingIncomingFiles, setPendingIncomingFiles] = React.useState([]);
const [connectionState, setConnectionState] = React.useState({
status: "disconnected",
hasActiveAnswer: false,
@@ -1899,6 +1909,13 @@ var EnhancedSecureP2PChat = () => {
} else {
addMessageWithAutoScroll(` File transfer error: ${error}`, "system");
}
},
// Incoming file request callback — receiver must explicitly accept before any data is sent
(fileRequest) => {
setPendingIncomingFiles((prev) => {
if (prev.some((f) => f.fileId === fileRequest.fileId)) return prev;
return [...prev, fileRequest];
});
}
);
}
@@ -3044,6 +3061,17 @@ var EnhancedSecureP2PChat = () => {
setPendingSession(null);
document.dispatchEvent(new CustomEvent("peer-disconnect"));
};
const handleIncomingDecision = React.useCallback(async (fileId, accepted) => {
try {
if (accepted) {
await webrtcManagerRef.current?.acceptIncomingFile(fileId);
} else {
await webrtcManagerRef.current?.rejectIncomingFile(fileId);
}
} finally {
setPendingIncomingFiles((prev) => prev.filter((f) => f.fileId !== fileId));
}
}, []);
const handleDisconnect = () => {
try {
setSessionTimeLeft(0);
@@ -3079,6 +3107,7 @@ var EnhancedSecureP2PChat = () => {
setShowQRScanner(false);
setShowQRScannerModal(false);
setMessages([]);
setPendingIncomingFiles([]);
if (typeof console.clear === "function") {
console.clear();
}
@@ -3236,7 +3265,9 @@ var EnhancedSecureP2PChat = () => {
isVerified,
chatMessagesRef,
scrollToBottom,
webrtcManager: webrtcManagerRef.current
webrtcManager: webrtcManagerRef.current,
pendingIncomingFiles,
onIncomingDecision: handleIncomingDecision
});
})() : React.createElement(EnhancedConnectionSetup, {
onCreateOffer: handleCreateOffer,