feat(qr-exchange): improved QR code exchange system
- Updated connection flow between users via QR codes - Added manual switching option in QR code generator - Increased number of QR codes for better readability
This commit is contained in:
202
dist/app.js
vendored
202
dist/app.js
vendored
@@ -1004,7 +1004,14 @@ var EnhancedConnectionSetup = ({
|
||||
answerPassword,
|
||||
localVerificationConfirmed,
|
||||
remoteVerificationConfirmed,
|
||||
bothVerificationsConfirmed
|
||||
bothVerificationsConfirmed,
|
||||
// QR control props
|
||||
qrFramesTotal,
|
||||
qrFrameIndex,
|
||||
qrManualMode,
|
||||
toggleQrManualMode,
|
||||
nextQrFrame,
|
||||
prevQrFrame
|
||||
}) => {
|
||||
const [mode, setMode] = React.useState("select");
|
||||
const resetToSelect = () => {
|
||||
@@ -1358,11 +1365,39 @@ var EnhancedConnectionSetup = ({
|
||||
src: qrCodeUrl,
|
||||
alt: "QR Code for secure connection",
|
||||
className: "max-w-none h-auto border border-gray-600/30 rounded w-[20rem] sm:w-[24rem] md:w-[28rem] lg:w-[32rem]"
|
||||
}),
|
||||
typeof qrFramesTotal !== "undefined" && typeof qrFrameIndex !== "undefined" && qrFramesTotal > 1 && React.createElement("div", {
|
||||
key: "qr-frame-indicator",
|
||||
className: "ml-3 self-center text-xs text-gray-300"
|
||||
}, `Frame ${Math.max(1, qrFrameIndex || 1)}/${qrFramesTotal}`)
|
||||
})
|
||||
]),
|
||||
// Переключатель управления ниже QR кода
|
||||
(qrFramesTotal || 0) >= 1 && React.createElement("div", {
|
||||
key: "qr-controls-below",
|
||||
className: "mt-4 flex flex-col items-center gap-2"
|
||||
}, [
|
||||
React.createElement("div", {
|
||||
key: "frame-indicator",
|
||||
className: "text-xs text-gray-300"
|
||||
}, `Frame ${Math.max(1, qrFrameIndex || 1)}/${qrFramesTotal || 1}`),
|
||||
React.createElement("div", {
|
||||
key: "control-buttons",
|
||||
className: "flex gap-1"
|
||||
}, [
|
||||
// Кнопки навигации показываем только если больше 1 части
|
||||
(qrFramesTotal || 0) > 1 && React.createElement("button", {
|
||||
key: "prev-frame",
|
||||
onClick: prevQrFrame,
|
||||
className: "w-6 h-6 bg-gray-600 hover:bg-gray-500 text-white rounded text-xs flex items-center justify-center"
|
||||
}, "\u25C0"),
|
||||
React.createElement("button", {
|
||||
key: "toggle-manual",
|
||||
onClick: toggleQrManualMode,
|
||||
className: `px-2 py-1 rounded text-xs font-medium ${qrManualMode || false ? "bg-blue-500 text-white" : "bg-gray-600 text-gray-300 hover:bg-gray-500"}`
|
||||
}, qrManualMode || false ? "Manual" : "Auto"),
|
||||
// Кнопки навигации показываем только если больше 1 части
|
||||
(qrFramesTotal || 0) > 1 && React.createElement("button", {
|
||||
key: "next-frame",
|
||||
onClick: nextQrFrame,
|
||||
className: "w-6 h-6 bg-gray-600 hover:bg-gray-500 text-white rounded text-xs flex items-center justify-center"
|
||||
}, "\u25B6")
|
||||
])
|
||||
]),
|
||||
React.createElement("p", {
|
||||
key: "qr-description",
|
||||
@@ -1679,6 +1714,63 @@ var EnhancedConnectionSetup = ({
|
||||
className: "w-full px-3 py-2 bg-green-500/10 hover:bg-green-500/20 text-green-400 border border-green-500/20 rounded text-sm font-medium"
|
||||
}, "Copy response code")
|
||||
]),
|
||||
// QR Code section for answer
|
||||
qrCodeUrl && React.createElement("div", {
|
||||
key: "qr-container",
|
||||
className: "mt-4 p-4 bg-gray-800/50 border border-gray-600/30 rounded-lg text-center"
|
||||
}, [
|
||||
React.createElement("h4", {
|
||||
key: "qr-title",
|
||||
className: "text-sm font-medium text-primary mb-3"
|
||||
}, "Scan QR code to complete connection"),
|
||||
React.createElement("div", {
|
||||
key: "qr-wrapper",
|
||||
className: "flex justify-center"
|
||||
}, [
|
||||
React.createElement("img", {
|
||||
key: "qr-image",
|
||||
src: qrCodeUrl,
|
||||
alt: "QR Code for secure response",
|
||||
className: "max-w-none h-auto border border-gray-600/30 rounded w-[20rem] sm:w-[24rem] md:w-[28rem] lg:w-[32rem]"
|
||||
})
|
||||
]),
|
||||
// Переключатель управления ниже QR кода
|
||||
(qrFramesTotal || 0) >= 1 && React.createElement("div", {
|
||||
key: "qr-controls-below",
|
||||
className: "mt-4 flex flex-col items-center gap-2"
|
||||
}, [
|
||||
React.createElement("div", {
|
||||
key: "frame-indicator",
|
||||
className: "text-xs text-gray-300"
|
||||
}, `Frame ${Math.max(1, qrFrameIndex || 1)}/${qrFramesTotal || 1}`),
|
||||
React.createElement("div", {
|
||||
key: "control-buttons",
|
||||
className: "flex gap-1"
|
||||
}, [
|
||||
// Кнопки навигации показываем только если больше 1 части
|
||||
(qrFramesTotal || 0) > 1 && React.createElement("button", {
|
||||
key: "prev-frame",
|
||||
onClick: prevQrFrame,
|
||||
className: "w-6 h-6 bg-gray-600 hover:bg-gray-500 text-white rounded text-xs flex items-center justify-center"
|
||||
}, "\u25C0"),
|
||||
React.createElement("button", {
|
||||
key: "toggle-manual",
|
||||
onClick: toggleQrManualMode,
|
||||
className: `px-2 py-1 rounded text-xs font-medium ${qrManualMode ? "bg-blue-500 text-white" : "bg-gray-600 text-gray-300 hover:bg-gray-500"}`
|
||||
}, qrManualMode ? "Manual" : "Auto"),
|
||||
// Кнопки навигации показываем только если больше 1 части
|
||||
(qrFramesTotal || 0) > 1 && React.createElement("button", {
|
||||
key: "next-frame",
|
||||
onClick: nextQrFrame,
|
||||
className: "w-6 h-6 bg-gray-600 hover:bg-gray-500 text-white rounded text-xs flex items-center justify-center"
|
||||
}, "\u25B6")
|
||||
])
|
||||
]),
|
||||
React.createElement("p", {
|
||||
key: "qr-description",
|
||||
className: "text-xs text-gray-400 mt-2"
|
||||
}, "The initiator can scan this QR code to complete the secure connection")
|
||||
]),
|
||||
React.createElement("div", {
|
||||
key: "info",
|
||||
className: "p-3 bg-purple-500/10 border border-purple-500/20 rounded-lg"
|
||||
@@ -2010,6 +2102,7 @@ var EnhancedChatInterface = ({
|
||||
};
|
||||
var EnhancedSecureP2PChat = () => {
|
||||
console.log("\u{1F50D} EnhancedSecureP2PChat component initialized");
|
||||
console.log("\u{1F3AE} QR Manual Control Features Loaded!");
|
||||
const [messages, setMessages] = React.useState([]);
|
||||
const [connectionStatus, setConnectionStatus] = React.useState("disconnected");
|
||||
const [messageInput, setMessageInput] = React.useState("");
|
||||
@@ -2052,18 +2145,21 @@ var EnhancedSecureP2PChat = () => {
|
||||
const shouldPreserveAnswerData = () => {
|
||||
const now = Date.now();
|
||||
const answerAge = now - (connectionState.answerCreatedAt || 0);
|
||||
const maxPreserveTime = 3e4;
|
||||
const maxPreserveTime = 3e5;
|
||||
const hasAnswerData = answerData && answerData.trim().length > 0 || answerInput && answerInput.trim().length > 0;
|
||||
const shouldPreserve = connectionState.hasActiveAnswer && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect || hasAnswerData && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect;
|
||||
const hasAnswerQR = qrCodeUrl && qrCodeUrl.trim().length > 0;
|
||||
const shouldPreserve = connectionState.hasActiveAnswer && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect || hasAnswerData && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect || hasAnswerQR && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect;
|
||||
console.log("\u{1F50D} shouldPreserveAnswerData check:", {
|
||||
hasActiveAnswer: connectionState.hasActiveAnswer,
|
||||
hasAnswerData,
|
||||
hasAnswerQR,
|
||||
answerAge,
|
||||
maxPreserveTime,
|
||||
isUserInitiatedDisconnect: connectionState.isUserInitiatedDisconnect,
|
||||
shouldPreserve,
|
||||
answerData: answerData ? "exists" : "null",
|
||||
answerInput: answerInput ? "exists" : "null"
|
||||
answerInput: answerInput ? "exists" : "null",
|
||||
qrCodeUrl: qrCodeUrl ? "exists" : "null"
|
||||
});
|
||||
return shouldPreserve;
|
||||
};
|
||||
@@ -2547,8 +2643,9 @@ var EnhancedSecureP2PChat = () => {
|
||||
return templateOffer;
|
||||
};
|
||||
const MAX_QR_LEN = 800;
|
||||
const [qrFramesTotal2, setQrFramesTotal] = React.useState(0);
|
||||
const [qrFrameIndex2, setQrFrameIndex] = React.useState(0);
|
||||
const [qrFramesTotal, setQrFramesTotal] = React.useState(0);
|
||||
const [qrFrameIndex, setQrFrameIndex] = React.useState(0);
|
||||
const [qrManualMode, setQrManualMode] = React.useState(false);
|
||||
const qrAnimationRef = React.useRef({ timer: null, chunks: [], idx: 0, active: false });
|
||||
const stopQrAnimation = () => {
|
||||
try {
|
||||
@@ -2560,6 +2657,48 @@ var EnhancedSecureP2PChat = () => {
|
||||
qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false };
|
||||
setQrFrameIndex(0);
|
||||
setQrFramesTotal(0);
|
||||
setQrManualMode(false);
|
||||
};
|
||||
const toggleQrManualMode = () => {
|
||||
const newManualMode = !qrManualMode;
|
||||
setQrManualMode(newManualMode);
|
||||
if (newManualMode) {
|
||||
if (qrAnimationRef.current.timer) {
|
||||
clearInterval(qrAnimationRef.current.timer);
|
||||
qrAnimationRef.current.timer = null;
|
||||
}
|
||||
console.log("QR Manual mode enabled - auto-scroll stopped");
|
||||
} else {
|
||||
if (qrAnimationRef.current.chunks.length > 1 && qrAnimationRef.current.active) {
|
||||
const intervalMs = 4e3;
|
||||
qrAnimationRef.current.timer = setInterval(renderNext, intervalMs);
|
||||
}
|
||||
console.log("QR Manual mode disabled - auto-scroll resumed");
|
||||
}
|
||||
};
|
||||
const nextQrFrame = () => {
|
||||
console.log("\u{1F3AE} nextQrFrame called, qrFramesTotal:", qrFramesTotal, "qrAnimationRef.current:", qrAnimationRef.current);
|
||||
if (qrAnimationRef.current.chunks.length > 1) {
|
||||
const nextIdx = (qrAnimationRef.current.idx + 1) % qrAnimationRef.current.chunks.length;
|
||||
qrAnimationRef.current.idx = nextIdx;
|
||||
setQrFrameIndex(nextIdx + 1);
|
||||
console.log("\u{1F3AE} Next frame index:", nextIdx + 1);
|
||||
renderNext();
|
||||
} else {
|
||||
console.log("\u{1F3AE} No multiple frames to navigate");
|
||||
}
|
||||
};
|
||||
const prevQrFrame = () => {
|
||||
console.log("\u{1F3AE} prevQrFrame called, qrFramesTotal:", qrFramesTotal, "qrAnimationRef.current:", qrAnimationRef.current);
|
||||
if (qrAnimationRef.current.chunks.length > 1) {
|
||||
const prevIdx = (qrAnimationRef.current.idx - 1 + qrAnimationRef.current.chunks.length) % qrAnimationRef.current.chunks.length;
|
||||
qrAnimationRef.current.idx = prevIdx;
|
||||
setQrFrameIndex(prevIdx + 1);
|
||||
console.log("\u{1F3AE} Previous frame index:", prevIdx + 1);
|
||||
renderNext();
|
||||
} else {
|
||||
console.log("\u{1F3AE} No multiple frames to navigate");
|
||||
}
|
||||
};
|
||||
const qrChunksBufferRef = React.useRef({ id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] });
|
||||
const generateQRCode2 = async (data) => {
|
||||
@@ -2581,8 +2720,10 @@ var EnhancedSecureP2PChat = () => {
|
||||
console.log("\u{1F39E}\uFE0F Using RAW animated QR frames (no compression)");
|
||||
stopQrAnimation();
|
||||
const id = `raw_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const FRAME_MAX = Math.max(300, Math.min(750, Math.floor(MAX_QR_LEN * 0.6)));
|
||||
const TARGET_CHUNKS = 10;
|
||||
const FRAME_MAX = Math.max(200, Math.floor(payload.length / TARGET_CHUNKS));
|
||||
const total = Math.ceil(payload.length / FRAME_MAX);
|
||||
console.log(`\u{1F4CA} Splitting ${payload.length} chars into ${total} chunks (max ${FRAME_MAX} chars per chunk)`);
|
||||
const rawChunks = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
const seq = i + 1;
|
||||
@@ -2603,7 +2744,7 @@ var EnhancedSecureP2PChat = () => {
|
||||
setQrFramesTotal(rawChunks.length);
|
||||
setQrFrameIndex(1);
|
||||
const EC_OPTS = { errorCorrectionLevel: "M", margin: 2, size: QR_SIZE };
|
||||
const renderNext = async () => {
|
||||
const renderNext2 = async () => {
|
||||
const { chunks, idx, active } = qrAnimationRef.current;
|
||||
if (!active || !chunks.length) return;
|
||||
const current = chunks[idx % chunks.length];
|
||||
@@ -2617,11 +2758,13 @@ var EnhancedSecureP2PChat = () => {
|
||||
qrAnimationRef.current.idx = nextIdx;
|
||||
setQrFrameIndex(nextIdx + 1);
|
||||
};
|
||||
await renderNext();
|
||||
const ua = typeof navigator !== "undefined" && navigator.userAgent ? navigator.userAgent : "";
|
||||
const isIOS = /iPhone|iPad|iPod/i.test(ua);
|
||||
const intervalMs = isIOS ? 2500 : 2e3;
|
||||
qrAnimationRef.current.timer = setInterval(renderNext, intervalMs);
|
||||
await renderNext2();
|
||||
if (!qrManualMode) {
|
||||
const ua = typeof navigator !== "undefined" && navigator.userAgent ? navigator.userAgent : "";
|
||||
const isIOS = /iPhone|iPad|iPod/i.test(ua);
|
||||
const intervalMs = 4e3;
|
||||
qrAnimationRef.current.timer = setInterval(renderNext2, intervalMs);
|
||||
}
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error("QR code generation failed:", error);
|
||||
@@ -2936,6 +3079,10 @@ var EnhancedSecureP2PChat = () => {
|
||||
console.log("Secure answer created:", answer);
|
||||
setAnswerData(answer);
|
||||
setShowAnswerStep(true);
|
||||
const answerString = typeof answer === "object" ? JSON.stringify(answer) : answer;
|
||||
console.log("Generating QR code for answer data length:", answerString.length);
|
||||
console.log("First 100 chars of answer data:", answerString.substring(0, 100));
|
||||
await generateQRCode2(answerString);
|
||||
markAnswerCreated2();
|
||||
const existingResponseMessages = messages.filter(
|
||||
(m) => m.type === "system" && (m.message.includes("Secure response created") || m.message.includes("Send the response"))
|
||||
@@ -2948,7 +3095,7 @@ var EnhancedSecureP2PChat = () => {
|
||||
timestamp: Date.now()
|
||||
}]);
|
||||
setMessages((prev) => [...prev, {
|
||||
message: "\u{1F4E4} Send the response code to the initiator via a secure channel..",
|
||||
message: "\u{1F4E4} Send the response code to the initiator via a secure channel or let them scan the QR code below.",
|
||||
type: "system",
|
||||
id: Date.now(),
|
||||
timestamp: Date.now()
|
||||
@@ -3163,12 +3310,16 @@ var EnhancedSecureP2PChat = () => {
|
||||
setOfferInput("");
|
||||
setAnswerInput("");
|
||||
setShowOfferStep(false);
|
||||
setShowAnswerStep(false);
|
||||
if (!shouldPreserveAnswerData()) {
|
||||
setShowAnswerStep(false);
|
||||
}
|
||||
setShowVerification(false);
|
||||
setShowQRCode(false);
|
||||
setShowQRScanner(false);
|
||||
setShowQRScannerModal(false);
|
||||
setQrCodeUrl("");
|
||||
if (!shouldPreserveAnswerData()) {
|
||||
setQrCodeUrl("");
|
||||
}
|
||||
setVerificationCode("");
|
||||
setIsVerified(false);
|
||||
setKeyFingerprint("");
|
||||
@@ -3327,7 +3478,14 @@ var EnhancedSecureP2PChat = () => {
|
||||
messages,
|
||||
localVerificationConfirmed,
|
||||
remoteVerificationConfirmed,
|
||||
bothVerificationsConfirmed
|
||||
bothVerificationsConfirmed,
|
||||
// QR control props
|
||||
qrFramesTotal,
|
||||
qrFrameIndex,
|
||||
qrManualMode,
|
||||
toggleQrManualMode,
|
||||
nextQrFrame,
|
||||
prevQrFrame
|
||||
// PAKE passwords removed - using SAS verification instead
|
||||
})
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user