150 lines
7.0 KiB
Markdown
150 lines
7.0 KiB
Markdown
# QR Code Large Data Rendering Fix
|
||
|
||
## Problem
|
||
|
||
QR codes fail to render (or render unscannable) when encoding large data payloads like eCash tokens (`cashuA...` / `cashuB...`). These tokens can be 1,500–3,000+ characters. Small payloads (short Lightning invoices, npubs) work fine.
|
||
|
||
## Root Cause Analysis
|
||
|
||
### Current Library
|
||
- **Library**: `datalog/qrcode-svg` ([GitHub](https://github.com/datalog/qrcode-svg)), MIT license
|
||
- **File**: `www/js/qrcode-svg.min.js`
|
||
- **Supports**: QR versions 1–40, all 4 error correction levels (L/M/Q/H)
|
||
|
||
### Current Usage in `renderQrCode()` (cashu.html:844–877)
|
||
|
||
```javascript
|
||
const svg = QRCode({ msg: value, pad });
|
||
```
|
||
|
||
**Only `msg` and `pad` are passed.** The library's other options are not used:
|
||
- `ecl` — Error Correction Level (defaults to `M` if not specified)
|
||
- `dim` — SVG dimension
|
||
- `pal` — Color palette
|
||
|
||
### Why It Fails
|
||
|
||
The `datalog/qrcode-svg` library has a **hard limit of QR Version 40** (177×177 modules). The maximum data capacity depends on the error correction level:
|
||
|
||
| ECL | Binary (bytes) | Alphanumeric (chars) |
|
||
|-----|---------------|---------------------|
|
||
| L (Low) | 2,953 | 4,296 |
|
||
| M (Medium) | 2,331 | 3,391 |
|
||
| Q (Quartile) | 1,663 | 2,420 |
|
||
| H (High) | 1,273 | 1,852 |
|
||
|
||
Cashu tokens are **mixed-case base64** strings, which means they **cannot use alphanumeric mode** (which only supports uppercase A-Z, 0-9, and a few symbols). They must be encoded in **byte mode**, where the Version 40 / ECL M limit is **2,331 bytes**.
|
||
|
||
With the default `ecl: 'M'`, tokens longer than ~2,331 characters will fail. Switching to `ecl: 'L'` raises the limit to **2,953 bytes** — a 27% increase.
|
||
|
||
However, even with `ecl: 'L'`, tokens exceeding ~2,953 characters will still fail because that is the absolute maximum for any standard QR code.
|
||
|
||
## Recommended Fix: Two-Phase Approach
|
||
|
||
### Phase 1: Quick Fix — Set `ecl: 'L'` for Cashu Tokens (Do This First)
|
||
|
||
Modify `renderQrCode()` in `www/cashu.html` to use the lowest error correction level for cashu tokens, maximizing data capacity:
|
||
|
||
```javascript
|
||
function renderQrCode(container, text) {
|
||
if (!container) return;
|
||
|
||
const raw = String(text || '').trim();
|
||
if (!raw) {
|
||
container.innerHTML = '';
|
||
container.classList.add('clsHidden');
|
||
return;
|
||
}
|
||
|
||
const isLightningInvoice = /^ln[a-z0-9]+/i.test(raw);
|
||
const isCashuToken = /^cashu[AB]/i.test(raw);
|
||
const value = isLightningInvoice ? raw.toUpperCase() : raw;
|
||
const pad = isLightningInvoice ? 8 : (isCashuToken ? 1 : 4);
|
||
const ecl = isCashuToken ? 'L' : (isLightningInvoice ? 'L' : 'M');
|
||
|
||
if (typeof QRCode !== 'function') {
|
||
console.warn('[cashu.html] QRCode generator not available');
|
||
container.innerHTML = '';
|
||
container.classList.add('clsHidden');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
container.innerHTML = '';
|
||
const svg = QRCode({ msg: value, pad, ecl });
|
||
svg.removeAttributeNS(null, 'width');
|
||
svg.removeAttributeNS(null, 'height');
|
||
container.appendChild(svg);
|
||
container.classList.remove('clsHidden');
|
||
} catch (error) {
|
||
console.error('[cashu.html] QR render failed:', error);
|
||
container.innerHTML = '<div style="color:var(--accent-color);font-size:80%;padding:10px;text-align:center;">Token too large for QR code. Use the copy button instead.</div>';
|
||
container.classList.remove('clsHidden');
|
||
}
|
||
}
|
||
```
|
||
|
||
Key changes:
|
||
1. **Detect cashu tokens** with `/^cashu[AB]/i`
|
||
2. **Set `ecl: 'L'`** for cashu tokens and lightning invoices (max capacity)
|
||
3. **Reduce `pad` to 1** for cashu tokens (more room for data modules)
|
||
4. **Show a helpful error message** instead of hiding the container on failure
|
||
|
||
### Phase 2: If Tokens Still Exceed 2,953 Bytes — Consider Alternatives
|
||
|
||
If eCash tokens regularly exceed ~2,953 characters, no standard QR code library can help — this is a QR specification limit. Options:
|
||
|
||
#### Option A: Uppercase the Cashu Token (if protocol allows)
|
||
If the cashu token can be uppercased without breaking it, the QR encoder can use **alphanumeric mode** which supports up to **4,296 characters** at ECL L. However, cashuA/cashuB tokens are base64-encoded, so uppercasing would corrupt them. **This is NOT viable for standard base64.**
|
||
|
||
#### Option B: Replace the Library with One That Has Better Diagnostics
|
||
The current library silently fails or throws. A more robust library could provide better error handling. Good open-source alternatives:
|
||
|
||
| Library | URL | Size | Format | Notes |
|
||
|---------|-----|------|--------|-------|
|
||
| **qrcode-generator** | https://github.com/niclas/qrcode-generator | ~15KB | JS | Mature, supports all versions/ECL, canvas + SVG |
|
||
| **qr-creator** | https://github.com/niclas/qr-creator | ~8KB | JS | Lightweight, SVG output, good error messages |
|
||
| **nayuki/QR-Code-generator** | https://github.com/nayuki/QR-Code-generator | ~20KB | JS/TS | Reference implementation, very well tested, supports all versions |
|
||
| **soldair/node-qrcode** | https://github.com/soldair/node-qrcode | ~30KB | JS | Most popular, canvas/SVG/terminal, excellent options |
|
||
|
||
**Recommendation**: The current `datalog/qrcode-svg` library is actually fine for the job — it supports all QR versions and ECL levels. The issue is purely that `ecl` is not being passed. Switching libraries is unnecessary unless we need features like:
|
||
- Structured Append (splitting data across multiple QR codes)
|
||
- Better error diagnostics
|
||
- Canvas rendering for performance
|
||
|
||
#### Option C: Multi-QR / Structured Append
|
||
For tokens exceeding 2,953 bytes, split the data across **multiple QR codes** using QR Structured Append (up to 16 QR codes). This requires:
|
||
- A library that supports structured append (most don't)
|
||
- UI changes to show multiple QR codes or an animated sequence
|
||
- The scanning app to support structured append (many don't)
|
||
|
||
#### Option D: Animated QR Codes (UR Protocol)
|
||
Use the **UR (Uniform Resources)** protocol from Blockchain Commons, which encodes large data as a sequence of animated QR frames. Libraries:
|
||
- **bc-ur** (https://github.com/niclas/bc-ur) — JavaScript implementation
|
||
- Used by many Bitcoin/crypto wallets (BlueWallet, Sparrow, etc.)
|
||
|
||
This is the most robust solution for very large payloads but requires both sender and receiver to support the UR protocol.
|
||
|
||
## Files to Modify
|
||
|
||
| File | Change |
|
||
|------|--------|
|
||
| `www/cashu.html` | Update `renderQrCode()` function (~lines 844-877) |
|
||
|
||
## Files Using QR Codes (for reference, no changes needed)
|
||
|
||
| File | Usage |
|
||
|------|-------|
|
||
| `www/npub.html:428` | `QRCode({ msg: currentNpub, pad: 0 })` — short data, fine |
|
||
| `www/index.html:563` | `QRCode({ msg: pubkey, dim: 170 })` — short data, fine |
|
||
| `www/skills-demo.html:1794` | `QRCode({ msg: value, dim: 220, pad: 1 })` — variable data |
|
||
| `www/ai.html:1555` | `QRCode({ msg: value, dim: 220, pad: 1 })` — variable data |
|
||
|
||
## Implementation Steps
|
||
|
||
1. Update `renderQrCode()` in `www/cashu.html` to pass `ecl: 'L'` for cashu tokens
|
||
2. Reduce padding for cashu tokens to maximize scannable area
|
||
3. Add graceful error handling with user-friendly message when token exceeds QR capacity
|
||
4. Test with real cashu tokens of various sizes
|
||
5. If tokens regularly exceed 2,953 bytes, evaluate UR protocol or multi-QR approach
|