fix(qr): accept a one-frame invitation; release v5.9.1
CodeQL Analysis / Analyze CodeQL (push) Canceled after 0s
Deploy Application / deploy (push) Canceled after 0s
Mirror to Codeberg / mirror (push) Canceled after 0s
Mirror to PrivacyGuides / mirror (push) Canceled after 0s

The scanner waited for four frames when shown a single one. Its chunk assembler
was written for SB1, which the generator always cuts into exactly four frames,
and its fallback branch claims any non-JSON string longer than 100 characters —
which a 151-character SBQ2 invitation is. A complete invitation was filed as
chunk 1 of 4, and the scan never finished.

SBQ2 payloads are now recognised as complete before any assembly runs, in both
the text and raw-byte forms, and the hard-coded frame count is marked as
belonging to SB1 so it is not read as a general rule.
This commit is contained in:
lockbitchat
2026-08-06 23:32:20 -04:00
parent 808fd99b73
commit 556727eb6f
13 changed files with 179 additions and 43 deletions
+34 -2
View File
@@ -4230,6 +4230,30 @@ import {
try {
console.log('QR Code scanned:', scannedData.substring(0, 100) + '...');
console.log('Current buffer state:', qrChunksBufferRef.current);
// An SBQ2 invitation is ONE frame, always. It has to be
// recognised before any chunk logic runs: the assembler
// below assumes a fixed four-frame split (that is what
// an SB1 payload is cut into) and the fallback branch
// claims every non-JSON string over 100 characters a
// 151-character SB2: code matches, is filed as chunk 1
// of 4, and the scanner waits for three frames that do
// not exist.
if (scannedData.startsWith('SB2:') || scannedData.charCodeAt(0) === 0x02) {
qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] };
if (showOfferStep) {
setAnswerInput(scannedData);
} else {
setOfferInput(scannedData);
}
setMessages(prev => [...prev, {
message: 'Invitation captured.',
type: 'success'
}]);
setShowQRScannerModal(false);
return Promise.resolve(true);
}
// 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)) {
@@ -4241,7 +4265,11 @@ import {
// Initialize buffer for binary chunks
qrChunksBufferRef.current = {
id: `bin_${Date.now()}`,
total: 4, // We expect 4 chunks
// SB1 payloads are split into exactly four
// frames by the generator above. SBQ2 never
// reaches here it is one frame and is
// handled at the top of this function.
total: 4,
seen: new Set(),
items: [],
lastUpdateMs: Date.now()
@@ -4321,7 +4349,11 @@ import {
console.log('Initializing buffer for potential binary chunks');
qrChunksBufferRef.current = {
id: `bin_${Date.now()}`,
total: 4, // We expect 4 chunks
// SB1 payloads are split into exactly four
// frames by the generator above. SBQ2 never
// reaches here it is one frame and is
// handled at the top of this function.
total: 4,
seen: new Set(),
items: [],
lastUpdateMs: Date.now()