qr-secure-send 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.html +64 -9
- package/package.json +1 -1
package/index.html
CHANGED
|
@@ -124,6 +124,7 @@
|
|
|
124
124
|
<header>
|
|
125
125
|
<h1>QR Secure Send</h1>
|
|
126
126
|
<p>Encrypt and transfer secrets via QR code</p>
|
|
127
|
+
<p style="font-size:0.7rem;color:#484f58;margin-top:0.2rem">v1.5.0</p>
|
|
127
128
|
</header>
|
|
128
129
|
|
|
129
130
|
<div class="tabs">
|
|
@@ -168,7 +169,9 @@
|
|
|
168
169
|
<div class="btn-row">
|
|
169
170
|
<button class="primary" id="scan-btn">Start Camera</button>
|
|
170
171
|
<button class="secondary" id="stop-btn" style="display:none">Stop</button>
|
|
172
|
+
<button class="primary" id="decrypt-btn" style="display:none">Decrypt</button>
|
|
171
173
|
</div>
|
|
174
|
+
<div id="link-notice" class="info" style="display:none">Encrypted data received via link. Enter the passphrase and click Decrypt.</div>
|
|
172
175
|
<div id="recv-error" class="error"></div>
|
|
173
176
|
<div id="compat-warning" class="warning" style="display:none"></div>
|
|
174
177
|
</div>
|
|
@@ -356,7 +359,7 @@
|
|
|
356
359
|
// -- QR matrix construction --
|
|
357
360
|
|
|
358
361
|
const ALIGNMENT_POSITIONS = [
|
|
359
|
-
null,[],[], [6,22],[6,26],[6,30],[6,34],
|
|
362
|
+
null,[],[6,18], [6,22],[6,26],[6,30],[6,34],
|
|
360
363
|
[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],
|
|
361
364
|
[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],
|
|
362
365
|
[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],
|
|
@@ -621,15 +624,15 @@
|
|
|
621
624
|
];
|
|
622
625
|
for (let i = 0; i < 15; i++) {
|
|
623
626
|
const [r, c] = positions1[i];
|
|
624
|
-
matrix[r][c] = ((bits >>
|
|
627
|
+
matrix[r][c] = ((bits >> i) & 1) ? 1 : 2;
|
|
625
628
|
}
|
|
626
629
|
|
|
627
|
-
// Bottom-left and top-right
|
|
630
|
+
// Bottom-left and top-right (opposite bit order from first copy)
|
|
628
631
|
for (let i = 0; i < 7; i++) {
|
|
629
|
-
matrix[size - 1 - i][8] = ((bits >> i) & 1) ? 1 : 2;
|
|
632
|
+
matrix[size - 1 - i][8] = ((bits >> (14 - i)) & 1) ? 1 : 2;
|
|
630
633
|
}
|
|
631
634
|
for (let i = 7; i < 15; i++) {
|
|
632
|
-
matrix[8][size - 15 + i] = ((bits >> i) & 1) ? 1 : 2;
|
|
635
|
+
matrix[8][size - 15 + i] = ((bits >> (14 - i)) & 1) ? 1 : 2;
|
|
633
636
|
}
|
|
634
637
|
}
|
|
635
638
|
|
|
@@ -905,6 +908,22 @@
|
|
|
905
908
|
}
|
|
906
909
|
|
|
907
910
|
|
|
911
|
+
// =========================================================================
|
|
912
|
+
// PAYLOAD HELPERS
|
|
913
|
+
// =========================================================================
|
|
914
|
+
|
|
915
|
+
const GH_PAGES_URL = "https://degenddy.github.io/qr-secure-send/";
|
|
916
|
+
|
|
917
|
+
// Extract the base64 encrypted data from either raw "QRSEC:..." or a URL with "#QRSEC:..."
|
|
918
|
+
function extractPayload(text) {
|
|
919
|
+
if (text.startsWith("QRSEC:")) return text.slice(6);
|
|
920
|
+
try {
|
|
921
|
+
const hash = new URL(text).hash;
|
|
922
|
+
if (hash.startsWith("#QRSEC:")) return hash.slice(7);
|
|
923
|
+
} catch (_) {}
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
|
|
908
927
|
// =========================================================================
|
|
909
928
|
// UI LOGIC
|
|
910
929
|
// =========================================================================
|
|
@@ -950,10 +969,10 @@
|
|
|
950
969
|
|
|
951
970
|
try {
|
|
952
971
|
const encrypted = await encryptSecret(secret, passphrase);
|
|
953
|
-
const payload = "QRSEC:" + encrypted;
|
|
972
|
+
const payload = GH_PAGES_URL + "#QRSEC:" + encrypted;
|
|
954
973
|
|
|
955
974
|
if (payload.length > 2953) {
|
|
956
|
-
errEl.textContent = "Secret is too long for a QR code. Keep it under ~
|
|
975
|
+
errEl.textContent = "Secret is too long for a QR code. Keep it under ~1950 characters.";
|
|
957
976
|
return;
|
|
958
977
|
}
|
|
959
978
|
|
|
@@ -991,12 +1010,13 @@
|
|
|
991
1010
|
const started = await QRScan.start(
|
|
992
1011
|
region,
|
|
993
1012
|
async (decodedText) => {
|
|
994
|
-
|
|
1013
|
+
const encrypted = extractPayload(decodedText);
|
|
1014
|
+
if (!encrypted) {
|
|
995
1015
|
errEl.textContent = "Not a QR Secure Send code.";
|
|
996
1016
|
return false; // keep scanning
|
|
997
1017
|
}
|
|
998
1018
|
try {
|
|
999
|
-
const secret = await decryptSecret(
|
|
1019
|
+
const secret = await decryptSecret(encrypted, passphrase);
|
|
1000
1020
|
document.getElementById("recv-value").textContent = secret;
|
|
1001
1021
|
resultBox.style.display = "block";
|
|
1002
1022
|
errEl.textContent = "";
|
|
@@ -1028,6 +1048,41 @@
|
|
|
1028
1048
|
document.getElementById("stop-btn").style.display = "none";
|
|
1029
1049
|
});
|
|
1030
1050
|
|
|
1051
|
+
// Decrypt button (for link-based flow)
|
|
1052
|
+
document.getElementById("decrypt-btn").addEventListener("click", async () => {
|
|
1053
|
+
const passphrase = document.getElementById("recv-passphrase").value;
|
|
1054
|
+
const errEl = document.getElementById("recv-error");
|
|
1055
|
+
const resultBox = document.getElementById("recv-result");
|
|
1056
|
+
errEl.textContent = "";
|
|
1057
|
+
resultBox.style.display = "none";
|
|
1058
|
+
|
|
1059
|
+
if (!passphrase) { errEl.textContent = "Enter the decryption passphrase first."; return; }
|
|
1060
|
+
|
|
1061
|
+
const hashData = location.hash.startsWith("#QRSEC:") ? location.hash.slice(7) : null;
|
|
1062
|
+
if (!hashData) { errEl.textContent = "No encrypted data found in URL."; return; }
|
|
1063
|
+
|
|
1064
|
+
try {
|
|
1065
|
+
const secret = await decryptSecret(hashData, passphrase);
|
|
1066
|
+
document.getElementById("recv-value").textContent = secret;
|
|
1067
|
+
resultBox.style.display = "block";
|
|
1068
|
+
} catch (e) {
|
|
1069
|
+
errEl.textContent = "Decryption failed. Wrong passphrase?";
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
// Check URL hash for incoming encrypted data (link-based receive flow)
|
|
1074
|
+
if (location.hash.startsWith("#QRSEC:")) {
|
|
1075
|
+
// Switch to receive tab
|
|
1076
|
+
document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
|
|
1077
|
+
document.querySelectorAll(".panel").forEach(p => p.classList.remove("active"));
|
|
1078
|
+
document.querySelector('[data-tab="receive"]').classList.add("active");
|
|
1079
|
+
document.getElementById("receive").classList.add("active");
|
|
1080
|
+
// Show decrypt button instead of camera controls
|
|
1081
|
+
document.getElementById("scan-btn").style.display = "none";
|
|
1082
|
+
document.getElementById("decrypt-btn").style.display = "inline-block";
|
|
1083
|
+
document.getElementById("link-notice").style.display = "block";
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1031
1086
|
// Copy to clipboard
|
|
1032
1087
|
document.getElementById("copy-btn").addEventListener("click", async () => {
|
|
1033
1088
|
const value = document.getElementById("recv-value").textContent;
|