Fix QR scanner multi-chunk processing and binary data handling
- Fix duplicate chunk detection by using data hash instead of index - Add comprehensive logging for QR scanner debugging - Implement proper buffer cleanup when scanner is closed - Preserve original binary data instead of decoding to JSON - Add deduplication logic to prevent same QR code being processed multiple times - Improve error handling and scanner state management - Fix binary chunk reconstruction to maintain SB1:bin: prefix format
This commit is contained in:
1 parent
e6d6576d70
commit
5b557102b4
4 files changed
+789
-62
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media Line truncated
|
||||
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media Line truncated
|
||||
Vendored
+346
-18
@@ -568,7 +568,7 @@ var EnhancedConnectionSetup = ({
|
||||
]),
|
||||
showQRCode && 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"
|
||||
className: "mt-4 p-4 border border-gray-600/30 rounded-lg text-center"
|
||||
}, [
|
||||
React.createElement("h4", {
|
||||
key: "qr-title",
|
||||
@@ -939,7 +939,7 @@ var EnhancedConnectionSetup = ({
|
||||
// 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"
|
||||
className: "mt-4 p-4 border border-gray-600/30 rounded-lg text-center"
|
||||
}, [
|
||||
React.createElement("h4", {
|
||||
key: "qr-title",
|
||||
@@ -1923,9 +1923,57 @@ var EnhancedSecureP2PChat = () => {
|
||||
const generateQRCode = async (data) => {
|
||||
try {
|
||||
const originalSize = typeof data === "string" ? data.length : JSON.stringify(data).length;
|
||||
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
||||
const isDesktop = typeof window !== "undefined" && (window.innerWidth || 0) >= 1024;
|
||||
const QR_SIZE = isDesktop ? 720 : 512;
|
||||
if (typeof window.generateBinaryQRCodeFromObject === "function") {
|
||||
try {
|
||||
const obj = typeof data === "string" ? JSON.parse(data) : data;
|
||||
const qrDataUrl = await window.generateBinaryQRCodeFromObject(obj, { errorCorrectionLevel: "M", size: QR_SIZE, margin: 2 });
|
||||
if (qrDataUrl) {
|
||||
try {
|
||||
if (qrAnimationRef.current && qrAnimationRef.current.timer) {
|
||||
clearInterval(qrAnimationRef.current.timer);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false };
|
||||
setQrFrameIndex(0);
|
||||
setQrFramesTotal(0);
|
||||
setQrManualMode(false);
|
||||
setQrCodeUrl(qrDataUrl);
|
||||
setQrFramesTotal(1);
|
||||
setQrFrameIndex(1);
|
||||
return;
|
||||
}
|
||||
} catch (e2) {
|
||||
console.warn("Binary QR generation failed, falling back to compressed:", e2?.message || e2);
|
||||
}
|
||||
}
|
||||
if (typeof window.generateCompressedQRCode === "function") {
|
||||
try {
|
||||
const payload2 = typeof data === "string" ? data : JSON.stringify(data);
|
||||
const qrDataUrl = await window.generateCompressedQRCode(payload2, { errorCorrectionLevel: "M", size: QR_SIZE, margin: 2 });
|
||||
if (qrDataUrl) {
|
||||
try {
|
||||
if (qrAnimationRef.current && qrAnimationRef.current.timer) {
|
||||
clearInterval(qrAnimationRef.current.timer);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false };
|
||||
setQrFrameIndex(0);
|
||||
setQrFramesTotal(0);
|
||||
setQrManualMode(false);
|
||||
setQrCodeUrl(qrDataUrl);
|
||||
setQrFramesTotal(1);
|
||||
setQrFrameIndex(1);
|
||||
return;
|
||||
}
|
||||
} catch (e2) {
|
||||
console.warn("Compressed QR generation failed, falling back to plain:", e2?.message || e2);
|
||||
}
|
||||
}
|
||||
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
||||
if (payload.length <= MAX_QR_LEN) {
|
||||
if (!window.generateQRCode) throw new Error("QR code generator unavailable");
|
||||
try {
|
||||
@@ -2060,6 +2108,127 @@ var EnhancedSecureP2PChat = () => {
|
||||
};
|
||||
const handleQRScan = async (scannedData) => {
|
||||
try {
|
||||
console.log("QR Code scanned:", scannedData.substring(0, 100) + "...");
|
||||
console.log("Current buffer state:", qrChunksBufferRef.current);
|
||||
if (scannedData.startsWith("SB1:bin:") || qrChunksBufferRef.current && qrChunksBufferRef.current.id) {
|
||||
console.log("Binary chunk detected:", scannedData.substring(0, 50) + "...");
|
||||
if (!qrChunksBufferRef.current.id) {
|
||||
console.log("Initializing buffer for binary chunks");
|
||||
qrChunksBufferRef.current = {
|
||||
id: `bin_${Date.now()}`,
|
||||
total: 4,
|
||||
// We expect 4 chunks
|
||||
seen: /* @__PURE__ */ new Set(),
|
||||
items: [],
|
||||
lastUpdateMs: Date.now()
|
||||
};
|
||||
}
|
||||
const chunkHash = scannedData.substring(0, 50);
|
||||
if (qrChunksBufferRef.current.seen.has(chunkHash)) {
|
||||
console.log(`Chunk already scanned, ignoring...`);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
qrChunksBufferRef.current.seen.add(chunkHash);
|
||||
qrChunksBufferRef.current.items.push(scannedData);
|
||||
qrChunksBufferRef.current.lastUpdateMs = Date.now();
|
||||
try {
|
||||
const uniqueCount = qrChunksBufferRef.current.seen.size;
|
||||
document.dispatchEvent(new CustomEvent("qr-scan-progress", {
|
||||
detail: {
|
||||
id: qrChunksBufferRef.current.id,
|
||||
seq: uniqueCount,
|
||||
total: qrChunksBufferRef.current.total
|
||||
}
|
||||
}));
|
||||
setQrFramesTotal(qrChunksBufferRef.current.total);
|
||||
setQrFrameIndex(uniqueCount);
|
||||
} catch {
|
||||
}
|
||||
const isComplete = qrChunksBufferRef.current.seen.size >= qrChunksBufferRef.current.total;
|
||||
console.log(`Chunks collected: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, complete: ${isComplete}`);
|
||||
if (!isComplete) {
|
||||
console.log(`Scanned chunk ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, waiting for more...`);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
try {
|
||||
const fullBinaryData = qrChunksBufferRef.current.items.join("");
|
||||
if (showOfferStep) {
|
||||
setAnswerInput(fullBinaryData);
|
||||
} else {
|
||||
setOfferInput(fullBinaryData);
|
||||
}
|
||||
setMessages((prev) => [...prev, {
|
||||
message: "All binary chunks captured. Payload reconstructed.",
|
||||
type: "success"
|
||||
}]);
|
||||
qrChunksBufferRef.current = { id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] };
|
||||
setShowQRScannerModal(false);
|
||||
return Promise.resolve(true);
|
||||
} catch (e2) {
|
||||
console.warn("Binary chunks reconstruction failed:", e2);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
if (scannedData.length > 100 && !scannedData.startsWith("{") && !scannedData.startsWith("[")) {
|
||||
console.log("Detected potential binary chunk (long non-JSON string):", scannedData.substring(0, 50) + "...");
|
||||
if (!qrChunksBufferRef.current.id) {
|
||||
console.log("Initializing buffer for potential binary chunks");
|
||||
qrChunksBufferRef.current = {
|
||||
id: `bin_${Date.now()}`,
|
||||
total: 4,
|
||||
// We expect 4 chunks
|
||||
seen: /* @__PURE__ */ new Set(),
|
||||
items: [],
|
||||
lastUpdateMs: Date.now()
|
||||
};
|
||||
}
|
||||
const chunkHash = scannedData.substring(0, 50);
|
||||
if (qrChunksBufferRef.current.seen.has(chunkHash)) {
|
||||
console.log(`Chunk already scanned, ignoring...`);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
qrChunksBufferRef.current.seen.add(chunkHash);
|
||||
qrChunksBufferRef.current.items.push(scannedData);
|
||||
qrChunksBufferRef.current.lastUpdateMs = Date.now();
|
||||
try {
|
||||
const uniqueCount = qrChunksBufferRef.current.seen.size;
|
||||
document.dispatchEvent(new CustomEvent("qr-scan-progress", {
|
||||
detail: {
|
||||
id: qrChunksBufferRef.current.id,
|
||||
seq: uniqueCount,
|
||||
total: qrChunksBufferRef.current.total
|
||||
}
|
||||
}));
|
||||
setQrFramesTotal(qrChunksBufferRef.current.total);
|
||||
setQrFrameIndex(uniqueCount);
|
||||
} catch {
|
||||
}
|
||||
const isComplete = qrChunksBufferRef.current.seen.size >= qrChunksBufferRef.current.total;
|
||||
console.log(`Chunks collected: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, complete: ${isComplete}`);
|
||||
if (!isComplete) {
|
||||
console.log(`Scanned chunk ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, waiting for more...`);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
try {
|
||||
const fullBinaryData = qrChunksBufferRef.current.items.join("");
|
||||
if (showOfferStep) {
|
||||
setAnswerInput(fullBinaryData);
|
||||
} else {
|
||||
setOfferInput(fullBinaryData);
|
||||
}
|
||||
setMessages((prev) => [...prev, {
|
||||
message: "All binary chunks captured. Payload reconstructed.",
|
||||
type: "success"
|
||||
}]);
|
||||
qrChunksBufferRef.current = { id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] };
|
||||
setShowQRScannerModal(false);
|
||||
return Promise.resolve(true);
|
||||
} catch (e2) {
|
||||
console.warn("Binary chunks reconstruction failed:", e2);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
console.log("Processing single QR code:", scannedData.substring(0, 50) + "...");
|
||||
let parsedData;
|
||||
if (typeof window.decodeAnyPayload === "function") {
|
||||
const any = window.decodeAnyPayload(scannedData);
|
||||
@@ -2072,6 +2241,7 @@ var EnhancedSecureP2PChat = () => {
|
||||
const maybeDecompressed = typeof window.decompressIfNeeded === "function" ? window.decompressIfNeeded(scannedData) : scannedData;
|
||||
parsedData = JSON.parse(maybeDecompressed);
|
||||
}
|
||||
console.log("Decoded data:", parsedData);
|
||||
if (parsedData.hdr && parsedData.body) {
|
||||
const { hdr } = parsedData;
|
||||
if (!qrChunksBufferRef.current.id || qrChunksBufferRef.current.id !== hdr.id) {
|
||||
@@ -2258,8 +2428,7 @@ var EnhancedSecureP2PChat = () => {
|
||||
try {
|
||||
if (typeof window.encodeBinaryToPrefixed === "function") {
|
||||
const bin = window.encodeBinaryToPrefixed(offerString);
|
||||
const id = `bin_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const TARGET_CHUNKS = 10;
|
||||
const TARGET_CHUNKS = 4;
|
||||
let FRAME_MAX = Math.max(200, Math.floor(bin.length / TARGET_CHUNKS));
|
||||
if (FRAME_MAX <= 0) FRAME_MAX = 200;
|
||||
let total = Math.ceil(bin.length / FRAME_MAX);
|
||||
@@ -2267,11 +2436,12 @@ var EnhancedSecureP2PChat = () => {
|
||||
total = 2;
|
||||
FRAME_MAX = Math.ceil(bin.length / 2) || 1;
|
||||
}
|
||||
const id = `bin_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const chunks = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
const seq = i + 1;
|
||||
const part = bin.slice(i * FRAME_MAX, (i + 1) * FRAME_MAX);
|
||||
chunks.push(JSON.stringify({ hdr: { v: 1, id, seq, total, rt: "bin" }, body: part }));
|
||||
chunks.push(part);
|
||||
}
|
||||
const isDesktop = typeof window !== "undefined" && (window.innerWidth || 0) >= 1024;
|
||||
const QR_SIZE = isDesktop ? 720 : 512;
|
||||
@@ -2295,9 +2465,15 @@ var EnhancedSecureP2PChat = () => {
|
||||
setShowQRCode(true);
|
||||
} catch {
|
||||
}
|
||||
} else {
|
||||
await generateQRCode(offer);
|
||||
try {
|
||||
setShowQRCode(true);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
} catch (e2) {
|
||||
console.warn("Offer QR precompute failed:", e2);
|
||||
console.warn("Offer QR generation failed:", e2);
|
||||
}
|
||||
const existingMessages = messages.filter(
|
||||
(m) => m.type === "system" && (m.message.includes("Secure invitation created") || m.message.includes("Send the encrypted code"))
|
||||
@@ -2372,8 +2548,7 @@ var EnhancedSecureP2PChat = () => {
|
||||
try {
|
||||
if (typeof window.encodeBinaryToPrefixed === "function") {
|
||||
const bin = window.encodeBinaryToPrefixed(answerString);
|
||||
const id = `ans_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const TARGET_CHUNKS = 10;
|
||||
const TARGET_CHUNKS = 4;
|
||||
let FRAME_MAX = Math.max(200, Math.floor(bin.length / TARGET_CHUNKS));
|
||||
if (FRAME_MAX <= 0) FRAME_MAX = 200;
|
||||
let total = Math.ceil(bin.length / FRAME_MAX);
|
||||
@@ -2381,11 +2556,12 @@ var EnhancedSecureP2PChat = () => {
|
||||
total = 2;
|
||||
FRAME_MAX = Math.ceil(bin.length / 2) || 1;
|
||||
}
|
||||
const id = `ans_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const chunks = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
const seq = i + 1;
|
||||
const part = bin.slice(i * FRAME_MAX, (i + 1) * FRAME_MAX);
|
||||
chunks.push(JSON.stringify({ hdr: { v: 1, id, seq, total, rt: "bin" }, body: part }));
|
||||
chunks.push(part);
|
||||
}
|
||||
const isDesktop = typeof window !== "undefined" && (window.innerWidth || 0) >= 1024;
|
||||
const QR_SIZE = isDesktop ? 720 : 512;
|
||||
@@ -2410,13 +2586,7 @@ var EnhancedSecureP2PChat = () => {
|
||||
} catch {
|
||||
}
|
||||
} else {
|
||||
let url = "";
|
||||
if (typeof window.generateCompressedQRCode === "function") {
|
||||
url = await window.generateCompressedQRCode(answerString);
|
||||
} else {
|
||||
url = await generateQRCode(answerString);
|
||||
}
|
||||
if (url) setQrCodeUrl(url);
|
||||
await generateQRCode(answer);
|
||||
try {
|
||||
setShowQRCode(true);
|
||||
} catch {
|
||||
@@ -2659,6 +2829,7 @@ var EnhancedSecureP2PChat = () => {
|
||||
setShowQRCode(false);
|
||||
setShowQRScanner(false);
|
||||
setShowQRScannerModal(false);
|
||||
qrChunksBufferRef.current = { id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] };
|
||||
if (!shouldPreserveAnswerData()) {
|
||||
setQrCodeUrl("");
|
||||
}
|
||||
@@ -2750,6 +2921,89 @@ var EnhancedSecureP2PChat = () => {
|
||||
addMessageWithAutoScroll(" All security features enabled by default", "system");
|
||||
}
|
||||
}, [isConnectedAndVerified, pendingSession, connectionStatus]);
|
||||
React.useEffect(() => {
|
||||
if (showQRScannerModal && window.Html5Qrcode) {
|
||||
const html5Qrcode = new window.Html5Qrcode("qr-reader");
|
||||
const config = {
|
||||
fps: 10
|
||||
// Убираем qrbox чтобы использовать всю область
|
||||
};
|
||||
let isScanning = true;
|
||||
html5Qrcode.start(
|
||||
{ facingMode: "environment" },
|
||||
// Use back camera
|
||||
config,
|
||||
(decodedText, decodedResult) => {
|
||||
if (!isScanning) {
|
||||
console.log("Scanner stopped, ignoring scan");
|
||||
return;
|
||||
}
|
||||
console.log("QR Code scanned:", decodedText);
|
||||
console.log("Current buffer state:", qrChunksBufferRef.current);
|
||||
handleQRScan(decodedText).then((success) => {
|
||||
console.log("QR scan result:", success);
|
||||
if (success) {
|
||||
console.log("Closing scanner and modal");
|
||||
isScanning = false;
|
||||
try {
|
||||
console.log("Stopping scanner...");
|
||||
html5Qrcode.stop().then(() => {
|
||||
console.log("Scanner stopped, clearing...");
|
||||
html5Qrcode.clear();
|
||||
setShowQRScannerModal(false);
|
||||
}).catch((err) => {
|
||||
console.log("Error stopping scanner:", err);
|
||||
try {
|
||||
html5Qrcode.clear();
|
||||
} catch (clearErr) {
|
||||
console.log("Error clearing scanner:", clearErr);
|
||||
}
|
||||
setShowQRScannerModal(false);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("Error in scanner cleanup:", err);
|
||||
setShowQRScannerModal(false);
|
||||
}
|
||||
} else {
|
||||
console.log("Continuing to scan for more chunks...");
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error("QR scan processing error:", error);
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
if (isScanning) {
|
||||
console.log("QR scan error (ignored):", error);
|
||||
}
|
||||
}
|
||||
).catch((err) => {
|
||||
console.error("QR Scanner start error:", err);
|
||||
setShowQRScannerModal(false);
|
||||
});
|
||||
return () => {
|
||||
isScanning = false;
|
||||
try {
|
||||
html5Qrcode.stop().then(() => {
|
||||
html5Qrcode.clear();
|
||||
}).catch((err) => {
|
||||
console.log("Scanner already stopped or error stopping:", err);
|
||||
try {
|
||||
html5Qrcode.clear();
|
||||
} catch (clearErr) {
|
||||
console.log("Error clearing scanner in cleanup:", clearErr);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("Error in cleanup:", err);
|
||||
try {
|
||||
html5Qrcode.clear();
|
||||
} catch (clearErr) {
|
||||
console.log("Error clearing scanner in cleanup:", clearErr);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [showQRScannerModal]);
|
||||
return React.createElement("div", {
|
||||
className: "minimal-bg min-h-screen"
|
||||
}, [
|
||||
@@ -2822,7 +3076,81 @@ var EnhancedSecureP2PChat = () => {
|
||||
// PAKE passwords removed - using SAS verification instead
|
||||
markAnswerCreated
|
||||
})
|
||||
)
|
||||
),
|
||||
// QR Scanner Modal
|
||||
showQRScannerModal && React.createElement("div", {
|
||||
key: "qr-scanner-modal",
|
||||
className: "fixed inset-0 bg-black/80 flex items-center justify-center z-50"
|
||||
}, [
|
||||
React.createElement("div", {
|
||||
key: "scanner-container",
|
||||
className: "bg-gray-900 rounded-lg p-4 max-w-2xl w-full mx-4"
|
||||
}, [
|
||||
React.createElement("div", {
|
||||
key: "scanner-header",
|
||||
className: "flex items-center justify-between mb-4"
|
||||
}, [
|
||||
React.createElement("h3", {
|
||||
key: "scanner-title",
|
||||
className: "text-lg font-medium text-white"
|
||||
}, "QR Code Scanner"),
|
||||
React.createElement("button", {
|
||||
key: "close-btn",
|
||||
onClick: () => {
|
||||
setShowQRScannerModal(false);
|
||||
qrChunksBufferRef.current = { id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] };
|
||||
},
|
||||
className: "text-gray-400 hover:text-white"
|
||||
}, [
|
||||
React.createElement("i", {
|
||||
key: "close-icon",
|
||||
className: "fas fa-times"
|
||||
})
|
||||
])
|
||||
]),
|
||||
React.createElement("div", {
|
||||
key: "scanner-content",
|
||||
className: "text-center"
|
||||
}, [
|
||||
React.createElement("p", {
|
||||
key: "scanner-description",
|
||||
className: "text-gray-400 mb-4"
|
||||
}, "Point your camera at the QR code to scan"),
|
||||
qrChunksBufferRef.current && qrChunksBufferRef.current.id && React.createElement("div", {
|
||||
key: "progress-indicator",
|
||||
className: "mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg"
|
||||
}, [
|
||||
React.createElement("p", {
|
||||
key: "progress-text",
|
||||
className: "text-blue-400 text-sm"
|
||||
}, `Scanned: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total} parts`),
|
||||
React.createElement("div", {
|
||||
key: "progress-bar",
|
||||
className: "w-full bg-gray-700 rounded-full h-2 mt-2"
|
||||
}, [
|
||||
React.createElement("div", {
|
||||
key: "progress-fill",
|
||||
className: "bg-blue-500 h-2 rounded-full transition-all duration-300",
|
||||
style: {
|
||||
width: `${qrChunksBufferRef.current.seen.size / qrChunksBufferRef.current.total * 100}%`
|
||||
}
|
||||
})
|
||||
])
|
||||
]),
|
||||
React.createElement("div", {
|
||||
key: "scanner-placeholder",
|
||||
id: "qr-reader",
|
||||
className: "w-full h-96 bg-gray-800 rounded-lg flex items-center justify-center",
|
||||
style: { minHeight: "400px" }
|
||||
}, [
|
||||
React.createElement("p", {
|
||||
key: "scanner-placeholder-text",
|
||||
className: "text-gray-500"
|
||||
}, "Camera will start here...")
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
};
|
||||
function initializeApp() {
|
||||
|
||||
Vendored
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["../src/app.jsx"],
|
||||
"sourcesContent": ["\r\n // Enhanced Copy Button with better UX\r\n const EnhancedCopyButton = ({ text, className = \"\", children }) => {\r\n const [copied, setCopied] = React.useState(false);\r\n \r\n const handleCopy = async () => {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n } catch (error) {\r\n console.error('Copy failed:', error);\r\n // Fallback for older browsers\r\n const textArea = document.createElement('textarea');\r\n textArea.value = text;\r\n document.body.appendChild(textArea);\r\n textArea.select();\r\n document.execCommand('copy');\r\n document.body.removeChild(textArea);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n }\r\n };\r\n \r\n return React.createElement('button', {\r\n onClick: handleCopy,\r\n className: `${className} transition-all duration-200`\r\n }, [\r\n React.createElement('i', {\r\n key: 'icon',\r\n className: `${copied ? 'fas fa-check accent-green' : 'fas fa-copy text-secondary'} mr-2`\r\n }),\r\n copied ? 'Copied!' : children\r\n ]);\r\n };\r\n \r\n // Verification Component\r\n const VerificationStep = ({ verificationCode, onConfirm, onReject, localConfirmed, remoteConfirmed, bothConfirmed }) => {\r\n return React.createElement('div', {\r\n className: \"card-minimal rounded-xl p-6 border-purple-500/20\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'header',\r\n className: \"flex items-center mb-4\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'icon',\r\n className: \"w-10 h-10 bg-purple-500/10 border border-purple-500/20 rounded-lg flex items-center justify-center mr-3\"\r\n }, [\r\n React.createElement('i', {\r\n className: 'fas fa-shield-alt accent-purple'\r\n })\r\n ]),\r\n React.createElement('h3', {\r\n key: 'title',\r\n className: \"text-lg font-medium text-primary\"\r\n }, \"Security verification\")\r\n ]),\r\n React.createElement('div', {\r\n key: 'content',\r\n className: \"space-y-4\"\r\n }, [\r\n React.createElement('p', {\r\n key: 'description',\r\n className: \"text-secondary text-sm\"\r\n }, \"Verify the security code with your contact via another communication channel (voice, SMS, etc.):\"),\r\n React.createElement('div', {\r\n key: 'code-display',\r\n className: \"text-center\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'code',\r\n className: \"verification-code text-2xl py-4\"\r\n }, verificationCode)\r\n ]),\r\n // Verification status indicators\r\n React.createElement('div', {\r\n key: 'verification-status',\r\n className: \"space-y-2\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'local-status',\r\n className: `flex items-center justify-between p-2 rounded-lg ${localConfirmed ? 'bg-green-500/10 border border-green-500/20' : 'bg-gray-500/10 border border-gray-500/20'}`\r\n }, [\r\n React.createElement('span', {\r\n Line truncated
|
||||
"mappings": "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 truncated
|
||||
"names": ["e", "offerData"]
|
||||
"sourcesContent": ["\r\n // Enhanced Copy Button with better UX\r\n const EnhancedCopyButton = ({ text, className = \"\", children }) => {\r\n const [copied, setCopied] = React.useState(false);\r\n \r\n const handleCopy = async () => {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n } catch (error) {\r\n console.error('Copy failed:', error);\r\n // Fallback for older browsers\r\n const textArea = document.createElement('textarea');\r\n textArea.value = text;\r\n document.body.appendChild(textArea);\r\n textArea.select();\r\n document.execCommand('copy');\r\n document.body.removeChild(textArea);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n }\r\n };\r\n \r\n return React.createElement('button', {\r\n onClick: handleCopy,\r\n className: `${className} transition-all duration-200`\r\n }, [\r\n React.createElement('i', {\r\n key: 'icon',\r\n className: `${copied ? 'fas fa-check accent-green' : 'fas fa-copy text-secondary'} mr-2`\r\n }),\r\n copied ? 'Copied!' : children\r\n ]);\r\n };\r\n \r\n // Verification Component\r\n const VerificationStep = ({ verificationCode, onConfirm, onReject, localConfirmed, remoteConfirmed, bothConfirmed }) => {\r\n return React.createElement('div', {\r\n className: \"card-minimal rounded-xl p-6 border-purple-500/20\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'header',\r\n className: \"flex items-center mb-4\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'icon',\r\n className: \"w-10 h-10 bg-purple-500/10 border border-purple-500/20 rounded-lg flex items-center justify-center mr-3\"\r\n }, [\r\n React.createElement('i', {\r\n className: 'fas fa-shield-alt accent-purple'\r\n })\r\n ]),\r\n React.createElement('h3', {\r\n key: 'title',\r\n className: \"text-lg font-medium text-primary\"\r\n }, \"Security verification\")\r\n ]),\r\n React.createElement('div', {\r\n key: 'content',\r\n className: \"space-y-4\"\r\n }, [\r\n React.createElement('p', {\r\n key: 'description',\r\n className: \"text-secondary text-sm\"\r\n }, \"Verify the security code with your contact via another communication channel (voice, SMS, etc.):\"),\r\n React.createElement('div', {\r\n key: 'code-display',\r\n className: \"text-center\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'code',\r\n className: \"verification-code text-2xl py-4\"\r\n }, verificationCode)\r\n ]),\r\n // Verification status indicators\r\n React.createElement('div', {\r\n key: 'verification-status',\r\n className: \"space-y-2\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'local-status',\r\n className: `flex items-center justify-between p-2 rounded-lg ${localConfirmed ? 'bg-green-500/10 border border-green-500/20' : 'bg-gray-500/10 border border-gray-500/20'}`\r\n }, [\r\n React.createElement('span', {\r\n Line truncated
|
||||
"mappings": "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 truncated
|
||||
"names": ["e", "offerData", "payload"]
|
||||
}
|
||||
+439
-40
@@ -595,7 +595,7 @@
|
||||
]),
|
||||
showQRCode && 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"
|
||||
className: "mt-4 p-4 border border-gray-600/30 rounded-lg text-center"
|
||||
}, [
|
||||
React.createElement('h4', {
|
||||
key: 'qr-title',
|
||||
@@ -835,10 +835,10 @@
|
||||
onChange: (e) => {
|
||||
setOfferInput(e.target.value);
|
||||
if (e.target.value.trim().length > 0) {
|
||||
if (typeof markAnswerCreated === 'function') {
|
||||
markAnswerCreated();
|
||||
}
|
||||
if (typeof markAnswerCreated === 'function') {
|
||||
markAnswerCreated();
|
||||
}
|
||||
}
|
||||
},
|
||||
rows: 8,
|
||||
placeholder: "Paste the encrypted invitation code or scan QR code...",
|
||||
@@ -859,17 +859,17 @@
|
||||
}),
|
||||
'Scan QR Code'
|
||||
]),
|
||||
React.createElement('button', {
|
||||
key: 'process-btn',
|
||||
onClick: onCreateAnswer,
|
||||
disabled: !offerInput.trim() || connectionStatus === 'connecting',
|
||||
className: "flex-1 btn-secondary text-white py-2 px-4 rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
className: 'fas fa-cogs mr-2'
|
||||
}),
|
||||
'Process invitation'
|
||||
])
|
||||
React.createElement('button', {
|
||||
key: 'process-btn',
|
||||
onClick: onCreateAnswer,
|
||||
disabled: !offerInput.trim() || connectionStatus === 'connecting',
|
||||
className: "flex-1 btn-secondary text-white py-2 px-4 rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
className: 'fas fa-cogs mr-2'
|
||||
}),
|
||||
'Process invitation'
|
||||
])
|
||||
]),
|
||||
showQRScanner && React.createElement('div', {
|
||||
key: 'qr-scanner',
|
||||
@@ -978,7 +978,7 @@
|
||||
// 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"
|
||||
className: "mt-4 p-4 border border-gray-600/30 rounded-lg text-center"
|
||||
}, [
|
||||
React.createElement('h4', {
|
||||
key: 'qr-title',
|
||||
@@ -2166,10 +2166,53 @@
|
||||
const generateQRCode = async (data) => {
|
||||
try {
|
||||
const originalSize = typeof data === 'string' ? data.length : JSON.stringify(data).length;
|
||||
// Small payload: прямой JSON в один QR (без сжатия, без обёрток)
|
||||
const payload = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024);
|
||||
const QR_SIZE = isDesktop ? 720 : 512;
|
||||
|
||||
// Try binary format first (CBOR + deflate + base64url)
|
||||
if (typeof window.generateBinaryQRCodeFromObject === 'function') {
|
||||
try {
|
||||
const obj = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
const qrDataUrl = await window.generateBinaryQRCodeFromObject(obj, { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 });
|
||||
if (qrDataUrl) {
|
||||
try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {}
|
||||
qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false };
|
||||
setQrFrameIndex(0);
|
||||
setQrFramesTotal(0);
|
||||
setQrManualMode(false);
|
||||
setQrCodeUrl(qrDataUrl);
|
||||
setQrFramesTotal(1);
|
||||
setQrFrameIndex(1);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Binary QR generation failed, falling back to compressed:', e?.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to compressed JSON
|
||||
if (typeof window.generateCompressedQRCode === 'function') {
|
||||
try {
|
||||
const payload = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
const qrDataUrl = await window.generateCompressedQRCode(payload, { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 });
|
||||
if (qrDataUrl) {
|
||||
try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {}
|
||||
qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false };
|
||||
setQrFrameIndex(0);
|
||||
setQrFramesTotal(0);
|
||||
setQrManualMode(false);
|
||||
setQrCodeUrl(qrDataUrl);
|
||||
setQrFramesTotal(1);
|
||||
setQrFrameIndex(1);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Compressed QR generation failed, falling back to plain:', e?.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback to plain JSON
|
||||
const payload = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
if (payload.length <= MAX_QR_LEN) {
|
||||
if (!window.generateQRCode) throw new Error('QR code generator unavailable');
|
||||
try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {}
|
||||
@@ -2184,6 +2227,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Large payload: разбиваем на фреймы (plain JSON)
|
||||
try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {}
|
||||
qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false };
|
||||
setQrFrameIndex(0);
|
||||
@@ -2214,12 +2258,12 @@
|
||||
setQrFramesTotal(rawChunks.length);
|
||||
setQrFrameIndex(1);
|
||||
const EC_OPTS = { errorCorrectionLevel: 'M', margin: 2, size: QR_SIZE };
|
||||
await renderNext();
|
||||
await renderNext();
|
||||
|
||||
if (!qrManualMode) {
|
||||
const intervalMs = 4000; // 4 seconds per frame for better readability
|
||||
qrAnimationRef.current.active = true;
|
||||
qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs);
|
||||
qrAnimationRef.current.active = true;
|
||||
qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs);
|
||||
}
|
||||
return;
|
||||
} catch (error) {
|
||||
@@ -2303,7 +2347,172 @@
|
||||
|
||||
const handleQRScan = async (scannedData) => {
|
||||
try {
|
||||
// Prefer binary (CBOR) decode, else gzip JSON, else raw JSON
|
||||
console.log('QR Code scanned:', scannedData.substring(0, 100) + '...');
|
||||
console.log('Current buffer state:', qrChunksBufferRef.current);
|
||||
|
||||
// Check if this is a binary chunk (starts with SB1:bin: or is a raw binary chunk)
|
||||
if (scannedData.startsWith('SB1:bin:') || (qrChunksBufferRef.current && qrChunksBufferRef.current.id)) {
|
||||
console.log('Binary chunk detected:', scannedData.substring(0, 50) + '...');
|
||||
|
||||
// This is a binary chunk - add to buffer
|
||||
if (!qrChunksBufferRef.current.id) {
|
||||
console.log('Initializing buffer for binary chunks');
|
||||
// Initialize buffer for binary chunks
|
||||
qrChunksBufferRef.current = {
|
||||
id: `bin_${Date.now()}`,
|
||||
total: 4, // We expect 4 chunks
|
||||
seen: new Set(),
|
||||
items: [],
|
||||
lastUpdateMs: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
// Add chunk to buffer (use data hash as identifier)
|
||||
const chunkHash = scannedData.substring(0, 50); // Use first 50 chars as hash
|
||||
|
||||
// Check if this chunk was already scanned
|
||||
if (qrChunksBufferRef.current.seen.has(chunkHash)) {
|
||||
console.log(`Chunk already scanned, ignoring...`);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
qrChunksBufferRef.current.seen.add(chunkHash);
|
||||
qrChunksBufferRef.current.items.push(scannedData);
|
||||
qrChunksBufferRef.current.lastUpdateMs = Date.now();
|
||||
|
||||
// Emit progress and force re-render
|
||||
try {
|
||||
const uniqueCount = qrChunksBufferRef.current.seen.size;
|
||||
document.dispatchEvent(new CustomEvent('qr-scan-progress', {
|
||||
detail: {
|
||||
id: qrChunksBufferRef.current.id,
|
||||
seq: uniqueCount,
|
||||
total: qrChunksBufferRef.current.total
|
||||
}
|
||||
}));
|
||||
|
||||
// Force re-render to update progress indicator
|
||||
setQrFramesTotal(qrChunksBufferRef.current.total);
|
||||
setQrFrameIndex(uniqueCount);
|
||||
} catch {}
|
||||
|
||||
// Check if we have all chunks
|
||||
const isComplete = qrChunksBufferRef.current.seen.size >= qrChunksBufferRef.current.total;
|
||||
console.log(`Chunks collected: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, complete: ${isComplete}`);
|
||||
|
||||
if (!isComplete) {
|
||||
// Keep scanner open for more chunks
|
||||
console.log(`Scanned chunk ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, waiting for more...`);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
// All chunks collected - reconstruct binary data
|
||||
try {
|
||||
const fullBinaryData = qrChunksBufferRef.current.items.join('');
|
||||
// Store the original binary data, not decoded JSON
|
||||
if (showOfferStep) {
|
||||
setAnswerInput(fullBinaryData);
|
||||
} else {
|
||||
setOfferInput(fullBinaryData);
|
||||
}
|
||||
|
||||
setMessages(prev => [...prev, {
|
||||
message: 'All binary chunks captured. Payload reconstructed.',
|
||||
type: 'success'
|
||||
}]);
|
||||
|
||||
// Clear buffer and close scanner
|
||||
qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] };
|
||||
setShowQRScannerModal(false);
|
||||
return Promise.resolve(true);
|
||||
} catch (e) {
|
||||
console.warn('Binary chunks reconstruction failed:', e);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this might be a binary chunk (long string without JSON structure)
|
||||
if (scannedData.length > 100 && !scannedData.startsWith('{') && !scannedData.startsWith('[')) {
|
||||
console.log('Detected potential binary chunk (long non-JSON string):', scannedData.substring(0, 50) + '...');
|
||||
|
||||
// Initialize buffer if not exists
|
||||
if (!qrChunksBufferRef.current.id) {
|
||||
console.log('Initializing buffer for potential binary chunks');
|
||||
qrChunksBufferRef.current = {
|
||||
id: `bin_${Date.now()}`,
|
||||
total: 4, // We expect 4 chunks
|
||||
seen: new Set(),
|
||||
items: [],
|
||||
lastUpdateMs: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
// Add chunk to buffer (use data hash as identifier)
|
||||
const chunkHash = scannedData.substring(0, 50); // Use first 50 chars as hash
|
||||
|
||||
// Check if this chunk was already scanned
|
||||
if (qrChunksBufferRef.current.seen.has(chunkHash)) {
|
||||
console.log(`Chunk already scanned, ignoring...`);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
qrChunksBufferRef.current.seen.add(chunkHash);
|
||||
qrChunksBufferRef.current.items.push(scannedData);
|
||||
qrChunksBufferRef.current.lastUpdateMs = Date.now();
|
||||
|
||||
// Force re-render to update progress indicator
|
||||
try {
|
||||
const uniqueCount = qrChunksBufferRef.current.seen.size;
|
||||
document.dispatchEvent(new CustomEvent('qr-scan-progress', {
|
||||
detail: {
|
||||
id: qrChunksBufferRef.current.id,
|
||||
seq: uniqueCount,
|
||||
total: qrChunksBufferRef.current.total
|
||||
}
|
||||
}));
|
||||
|
||||
// Force re-render to update progress indicator
|
||||
setQrFramesTotal(qrChunksBufferRef.current.total);
|
||||
setQrFrameIndex(uniqueCount);
|
||||
} catch {}
|
||||
|
||||
// Check if we have all chunks
|
||||
const isComplete = qrChunksBufferRef.current.seen.size >= qrChunksBufferRef.current.total;
|
||||
console.log(`Chunks collected: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, complete: ${isComplete}`);
|
||||
|
||||
if (!isComplete) {
|
||||
// Keep scanner open for more chunks
|
||||
console.log(`Scanned chunk ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, waiting for more...`);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
// All chunks collected - reconstruct binary data
|
||||
try {
|
||||
const fullBinaryData = qrChunksBufferRef.current.items.join('');
|
||||
// Store the original binary data, not decoded JSON
|
||||
if (showOfferStep) {
|
||||
setAnswerInput(fullBinaryData);
|
||||
} else {
|
||||
setOfferInput(fullBinaryData);
|
||||
}
|
||||
|
||||
setMessages(prev => [...prev, {
|
||||
message: 'All binary chunks captured. Payload reconstructed.',
|
||||
type: 'success'
|
||||
}]);
|
||||
|
||||
// Clear buffer and close scanner
|
||||
qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] };
|
||||
setShowQRScannerModal(false);
|
||||
return Promise.resolve(true);
|
||||
} catch (e) {
|
||||
console.warn('Binary chunks reconstruction failed:', e);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Single QR code - try to decode directly
|
||||
console.log('Processing single QR code:', scannedData.substring(0, 50) + '...');
|
||||
let parsedData;
|
||||
if (typeof window.decodeAnyPayload === 'function') {
|
||||
const any = window.decodeAnyPayload(scannedData);
|
||||
@@ -2316,6 +2525,7 @@
|
||||
const maybeDecompressed = (typeof window.decompressIfNeeded === 'function') ? window.decompressIfNeeded(scannedData) : scannedData;
|
||||
parsedData = JSON.parse(maybeDecompressed);
|
||||
}
|
||||
console.log('Decoded data:', parsedData);
|
||||
|
||||
// QR with hdr/body: COSE or RAW/BIN animated frames
|
||||
if (parsedData.hdr && parsedData.body) {
|
||||
@@ -2529,24 +2739,27 @@
|
||||
setOfferData(offer);
|
||||
setShowOfferStep(true);
|
||||
|
||||
// Do not auto-generate single QR; prepare animated binary frames when user opens QR
|
||||
// Generate QR code with binary format and chunking
|
||||
const offerString = typeof offer === 'object' ? JSON.stringify(offer) : offer;
|
||||
try {
|
||||
if (typeof window.encodeBinaryToPrefixed === 'function') {
|
||||
const bin = window.encodeBinaryToPrefixed(offerString);
|
||||
// Precompute frames to be ready instantly on show
|
||||
const id = `bin_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const TARGET_CHUNKS = 10;
|
||||
// Force chunking into 4 parts - split binary data directly
|
||||
const TARGET_CHUNKS = 4;
|
||||
let FRAME_MAX = Math.max(200, Math.floor(bin.length / TARGET_CHUNKS));
|
||||
if (FRAME_MAX <= 0) FRAME_MAX = 200;
|
||||
let total = Math.ceil(bin.length / FRAME_MAX);
|
||||
if (total < 2) { total = 2; FRAME_MAX = Math.ceil(bin.length / 2) || 1; }
|
||||
|
||||
const id = `bin_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const chunks = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
const seq = i + 1;
|
||||
const part = bin.slice(i * FRAME_MAX, (i + 1) * FRAME_MAX);
|
||||
chunks.push(JSON.stringify({ hdr: { v: 1, id, seq, total, rt: 'bin' }, body: part }));
|
||||
// Store binary chunks directly without JSON wrapper
|
||||
chunks.push(part);
|
||||
}
|
||||
|
||||
// Seed first frame and start auto-advance immediately
|
||||
const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024);
|
||||
const QR_SIZE = isDesktop ? 720 : 512;
|
||||
@@ -2554,20 +2767,27 @@
|
||||
const firstUrl = await window.generateQRCode(chunks[0], { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 });
|
||||
if (firstUrl) setQrCodeUrl(firstUrl);
|
||||
}
|
||||
|
||||
// Store precomputed chunks to ref, ready for animation
|
||||
try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {}
|
||||
qrAnimationRef.current = { timer: null, chunks, idx: 0, active: true };
|
||||
setQrFramesTotal(chunks.length);
|
||||
setQrFrameIndex(1);
|
||||
setQrManualMode(false);
|
||||
|
||||
// Start auto-advance loop for Offer immediately
|
||||
const intervalMs = 3000;
|
||||
qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs);
|
||||
|
||||
// Show QR immediately for Offer flow
|
||||
try { setShowQRCode(true); } catch {}
|
||||
} else {
|
||||
// Fallback to single QR
|
||||
await generateQRCode(offer);
|
||||
try { setShowQRCode(true); } catch {}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Offer QR precompute failed:', e);
|
||||
console.warn('Offer QR generation failed:', e);
|
||||
}
|
||||
|
||||
const existingMessages = messages.filter(m =>
|
||||
@@ -2656,46 +2876,48 @@
|
||||
setAnswerData(answer);
|
||||
setShowAnswerStep(true);
|
||||
|
||||
// Answer QR: precompute binary frames and start animation immediately
|
||||
// Generate QR code with binary format and chunking
|
||||
const answerString = typeof answer === 'object' ? JSON.stringify(answer) : answer;
|
||||
try {
|
||||
if (typeof window.encodeBinaryToPrefixed === 'function') {
|
||||
const bin = window.encodeBinaryToPrefixed(answerString);
|
||||
const id = `ans_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const TARGET_CHUNKS = 10;
|
||||
// Force chunking into 4 parts - split binary data directly
|
||||
const TARGET_CHUNKS = 4;
|
||||
let FRAME_MAX = Math.max(200, Math.floor(bin.length / TARGET_CHUNKS));
|
||||
if (FRAME_MAX <= 0) FRAME_MAX = 200;
|
||||
let total = Math.ceil(bin.length / FRAME_MAX);
|
||||
if (total < 2) { total = 2; FRAME_MAX = Math.ceil(bin.length / 2) || 1; }
|
||||
|
||||
const id = `ans_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
const chunks = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
const seq = i + 1;
|
||||
const part = bin.slice(i * FRAME_MAX, (i + 1) * FRAME_MAX);
|
||||
chunks.push(JSON.stringify({ hdr: { v: 1, id, seq, total, rt: 'bin' }, body: part }));
|
||||
// Store binary chunks directly without JSON wrapper
|
||||
chunks.push(part);
|
||||
}
|
||||
|
||||
const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024);
|
||||
const QR_SIZE = isDesktop ? 720 : 512;
|
||||
if (window.generateQRCode && chunks.length > 0) {
|
||||
const firstUrl = await window.generateQRCode(chunks[0], { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 });
|
||||
if (firstUrl) setQrCodeUrl(firstUrl);
|
||||
}
|
||||
|
||||
try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {}
|
||||
qrAnimationRef.current = { timer: null, chunks, idx: 0, active: true };
|
||||
setQrFramesTotal(chunks.length);
|
||||
setQrFrameIndex(1);
|
||||
setQrManualMode(false);
|
||||
|
||||
const intervalMs = 3000;
|
||||
qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs);
|
||||
|
||||
// Show QR immediately for Answer flow
|
||||
try { setShowQRCode(true); } catch {}
|
||||
} else {
|
||||
// Fallback: single QR compressed or plain
|
||||
let url = '';
|
||||
if (typeof window.generateCompressedQRCode === 'function') {
|
||||
url = await window.generateCompressedQRCode(answerString);
|
||||
} else {
|
||||
url = await generateQRCode(answerString);
|
||||
}
|
||||
if (url) setQrCodeUrl(url);
|
||||
// Fallback to single QR
|
||||
await generateQRCode(answer);
|
||||
try { setShowQRCode(true); } catch {}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -2989,6 +3211,8 @@
|
||||
setShowQRCode(false);
|
||||
setShowQRScanner(false);
|
||||
setShowQRScannerModal(false);
|
||||
// Clear QR scanner buffer
|
||||
qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] };
|
||||
|
||||
if (!shouldPreserveAnswerData()) {
|
||||
setQrCodeUrl('');
|
||||
@@ -3117,6 +3341,105 @@
|
||||
addMessageWithAutoScroll(' All security features enabled by default', 'system');
|
||||
}
|
||||
}, [isConnectedAndVerified, pendingSession, connectionStatus]);
|
||||
|
||||
// QR Scanner initialization
|
||||
React.useEffect(() => {
|
||||
if (showQRScannerModal && window.Html5Qrcode) {
|
||||
const html5Qrcode = new window.Html5Qrcode("qr-reader");
|
||||
const config = {
|
||||
fps: 10
|
||||
// Убираем qrbox чтобы использовать всю область
|
||||
};
|
||||
|
||||
let isScanning = true;
|
||||
|
||||
html5Qrcode.start(
|
||||
{ facingMode: "environment" }, // Use back camera
|
||||
config,
|
||||
(decodedText, decodedResult) => {
|
||||
if (!isScanning) {
|
||||
console.log('Scanner stopped, ignoring scan');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('QR Code scanned:', decodedText);
|
||||
console.log('Current buffer state:', qrChunksBufferRef.current);
|
||||
|
||||
handleQRScan(decodedText).then((success) => {
|
||||
console.log('QR scan result:', success);
|
||||
if (success) {
|
||||
// Successfully processed - stop scanner and close modal
|
||||
console.log('Closing scanner and modal');
|
||||
isScanning = false;
|
||||
|
||||
// Stop scanner first, then clear
|
||||
try {
|
||||
console.log('Stopping scanner...');
|
||||
html5Qrcode.stop().then(() => {
|
||||
console.log('Scanner stopped, clearing...');
|
||||
html5Qrcode.clear();
|
||||
setShowQRScannerModal(false);
|
||||
}).catch((err) => {
|
||||
console.log('Error stopping scanner:', err);
|
||||
// Try to clear anyway
|
||||
try {
|
||||
html5Qrcode.clear();
|
||||
} catch (clearErr) {
|
||||
console.log('Error clearing scanner:', clearErr);
|
||||
}
|
||||
setShowQRScannerModal(false);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Error in scanner cleanup:', err);
|
||||
setShowQRScannerModal(false);
|
||||
}
|
||||
} else {
|
||||
console.log('Continuing to scan for more chunks...');
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('QR scan processing error:', error);
|
||||
// Continue scanning on error
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
// Ignore scanning errors - continue scanning
|
||||
if (isScanning) {
|
||||
console.log('QR scan error (ignored):', error);
|
||||
}
|
||||
}
|
||||
).catch((err) => {
|
||||
console.error('QR Scanner start error:', err);
|
||||
// Close modal on start error
|
||||
setShowQRScannerModal(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
isScanning = false;
|
||||
try {
|
||||
// Try to stop scanner, but don't worry if it's already stopped
|
||||
html5Qrcode.stop().then(() => {
|
||||
html5Qrcode.clear();
|
||||
}).catch((err) => {
|
||||
// Scanner might already be stopped, just clear it
|
||||
console.log('Scanner already stopped or error stopping:', err);
|
||||
try {
|
||||
html5Qrcode.clear();
|
||||
} catch (clearErr) {
|
||||
console.log('Error clearing scanner in cleanup:', clearErr);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Error in cleanup:', err);
|
||||
// Just try to clear, don't worry about stopping
|
||||
try {
|
||||
html5Qrcode.clear();
|
||||
} catch (clearErr) {
|
||||
console.log('Error clearing scanner in cleanup:', clearErr);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [showQRScannerModal]);
|
||||
|
||||
return React.createElement('div', {
|
||||
className: "minimal-bg min-h-screen"
|
||||
@@ -3193,6 +3516,82 @@
|
||||
})
|
||||
),
|
||||
|
||||
// QR Scanner Modal
|
||||
showQRScannerModal && React.createElement('div', {
|
||||
key: 'qr-scanner-modal',
|
||||
className: "fixed inset-0 bg-black/80 flex items-center justify-center z-50"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'scanner-container',
|
||||
className: "bg-gray-900 rounded-lg p-4 max-w-2xl w-full mx-4"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'scanner-header',
|
||||
className: "flex items-center justify-between mb-4"
|
||||
}, [
|
||||
React.createElement('h3', {
|
||||
key: 'scanner-title',
|
||||
className: "text-lg font-medium text-white"
|
||||
}, 'QR Code Scanner'),
|
||||
React.createElement('button', {
|
||||
key: 'close-btn',
|
||||
onClick: () => {
|
||||
setShowQRScannerModal(false);
|
||||
// Clear QR scanner buffer
|
||||
qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] };
|
||||
},
|
||||
className: "text-gray-400 hover:text-white"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'close-icon',
|
||||
className: 'fas fa-times'
|
||||
})
|
||||
])
|
||||
]),
|
||||
React.createElement('div', {
|
||||
key: 'scanner-content',
|
||||
className: "text-center"
|
||||
}, [
|
||||
React.createElement('p', {
|
||||
key: 'scanner-description',
|
||||
className: "text-gray-400 mb-4"
|
||||
}, 'Point your camera at the QR code to scan'),
|
||||
qrChunksBufferRef.current && qrChunksBufferRef.current.id && React.createElement('div', {
|
||||
key: 'progress-indicator',
|
||||
className: "mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg"
|
||||
}, [
|
||||
React.createElement('p', {
|
||||
key: 'progress-text',
|
||||
className: "text-blue-400 text-sm"
|
||||
}, `Scanned: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total} parts`),
|
||||
React.createElement('div', {
|
||||
key: 'progress-bar',
|
||||
className: "w-full bg-gray-700 rounded-full h-2 mt-2"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'progress-fill',
|
||||
className: "bg-blue-500 h-2 rounded-full transition-all duration-300",
|
||||
style: {
|
||||
width: `${(qrChunksBufferRef.current.seen.size / qrChunksBufferRef.current.total) * 100}%`
|
||||
}
|
||||
})
|
||||
])
|
||||
]),
|
||||
React.createElement('div', {
|
||||
key: 'scanner-placeholder',
|
||||
id: "qr-reader",
|
||||
className: "w-full h-96 bg-gray-800 rounded-lg flex items-center justify-center",
|
||||
style: { minHeight: '400px' }
|
||||
}, [
|
||||
React.createElement('p', {
|
||||
key: 'scanner-placeholder-text',
|
||||
className: "text-gray-500"
|
||||
}, 'Camera will start here...')
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
|
||||
]);
|
||||
};
|
||||
function initializeApp() {
|
||||
|
||||
Reference in new issue
Block a user