qr-secure-send 1.4.1 → 1.5.1
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 -5
- package/package.json +1 -1
package/index.html
CHANGED
|
@@ -124,7 +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.
|
|
127
|
+
<p style="font-size:0.7rem;color:#484f58;margin-top:0.2rem">v1.5.1</p>
|
|
128
128
|
</header>
|
|
129
129
|
|
|
130
130
|
<div class="tabs">
|
|
@@ -169,7 +169,9 @@
|
|
|
169
169
|
<div class="btn-row">
|
|
170
170
|
<button class="primary" id="scan-btn">Start Camera</button>
|
|
171
171
|
<button class="secondary" id="stop-btn" style="display:none">Stop</button>
|
|
172
|
+
<button class="primary" id="decrypt-btn" style="display:none">Decrypt</button>
|
|
172
173
|
</div>
|
|
174
|
+
<div id="link-notice" class="info" style="display:none">Encrypted data received via link. Enter the passphrase and click Decrypt.</div>
|
|
173
175
|
<div id="recv-error" class="error"></div>
|
|
174
176
|
<div id="compat-warning" class="warning" style="display:none"></div>
|
|
175
177
|
</div>
|
|
@@ -183,6 +185,11 @@
|
|
|
183
185
|
</div>
|
|
184
186
|
</div>
|
|
185
187
|
|
|
188
|
+
<div style="margin:2rem auto;max-width:480px;padding:0.8rem 1rem;background:#0d4429;border:1px solid #1a7f42;border-radius:8px;color:#3fb950;font-size:0.82rem;text-align:center;line-height:1.5">
|
|
189
|
+
This app runs <strong>entirely in your browser</strong>. Nothing is sent to any server — all encryption and decryption happen locally using the Web Crypto API.
|
|
190
|
+
<br>For fully offline use: <code style="background:#161b22;padding:0.15rem 0.4rem;border-radius:4px;color:#58a6ff">npx qr-secure-send</code>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
186
193
|
<script>
|
|
187
194
|
// =========================================================================
|
|
188
195
|
// QR CODE ENCODER — Zero dependencies, inline implementation
|
|
@@ -906,6 +913,22 @@
|
|
|
906
913
|
}
|
|
907
914
|
|
|
908
915
|
|
|
916
|
+
// =========================================================================
|
|
917
|
+
// PAYLOAD HELPERS
|
|
918
|
+
// =========================================================================
|
|
919
|
+
|
|
920
|
+
const GH_PAGES_URL = "https://degenddy.github.io/qr-secure-send/";
|
|
921
|
+
|
|
922
|
+
// Extract the base64 encrypted data from either raw "QRSEC:..." or a URL with "#QRSEC:..."
|
|
923
|
+
function extractPayload(text) {
|
|
924
|
+
if (text.startsWith("QRSEC:")) return text.slice(6);
|
|
925
|
+
try {
|
|
926
|
+
const hash = new URL(text).hash;
|
|
927
|
+
if (hash.startsWith("#QRSEC:")) return hash.slice(7);
|
|
928
|
+
} catch (_) {}
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
|
|
909
932
|
// =========================================================================
|
|
910
933
|
// UI LOGIC
|
|
911
934
|
// =========================================================================
|
|
@@ -951,10 +974,10 @@
|
|
|
951
974
|
|
|
952
975
|
try {
|
|
953
976
|
const encrypted = await encryptSecret(secret, passphrase);
|
|
954
|
-
const payload = "QRSEC:" + encrypted;
|
|
977
|
+
const payload = GH_PAGES_URL + "#QRSEC:" + encrypted;
|
|
955
978
|
|
|
956
979
|
if (payload.length > 2953) {
|
|
957
|
-
errEl.textContent = "Secret is too long for a QR code. Keep it under ~
|
|
980
|
+
errEl.textContent = "Secret is too long for a QR code. Keep it under ~1950 characters.";
|
|
958
981
|
return;
|
|
959
982
|
}
|
|
960
983
|
|
|
@@ -992,12 +1015,13 @@
|
|
|
992
1015
|
const started = await QRScan.start(
|
|
993
1016
|
region,
|
|
994
1017
|
async (decodedText) => {
|
|
995
|
-
|
|
1018
|
+
const encrypted = extractPayload(decodedText);
|
|
1019
|
+
if (!encrypted) {
|
|
996
1020
|
errEl.textContent = "Not a QR Secure Send code.";
|
|
997
1021
|
return false; // keep scanning
|
|
998
1022
|
}
|
|
999
1023
|
try {
|
|
1000
|
-
const secret = await decryptSecret(
|
|
1024
|
+
const secret = await decryptSecret(encrypted, passphrase);
|
|
1001
1025
|
document.getElementById("recv-value").textContent = secret;
|
|
1002
1026
|
resultBox.style.display = "block";
|
|
1003
1027
|
errEl.textContent = "";
|
|
@@ -1029,6 +1053,41 @@
|
|
|
1029
1053
|
document.getElementById("stop-btn").style.display = "none";
|
|
1030
1054
|
});
|
|
1031
1055
|
|
|
1056
|
+
// Decrypt button (for link-based flow)
|
|
1057
|
+
document.getElementById("decrypt-btn").addEventListener("click", async () => {
|
|
1058
|
+
const passphrase = document.getElementById("recv-passphrase").value;
|
|
1059
|
+
const errEl = document.getElementById("recv-error");
|
|
1060
|
+
const resultBox = document.getElementById("recv-result");
|
|
1061
|
+
errEl.textContent = "";
|
|
1062
|
+
resultBox.style.display = "none";
|
|
1063
|
+
|
|
1064
|
+
if (!passphrase) { errEl.textContent = "Enter the decryption passphrase first."; return; }
|
|
1065
|
+
|
|
1066
|
+
const hashData = location.hash.startsWith("#QRSEC:") ? location.hash.slice(7) : null;
|
|
1067
|
+
if (!hashData) { errEl.textContent = "No encrypted data found in URL."; return; }
|
|
1068
|
+
|
|
1069
|
+
try {
|
|
1070
|
+
const secret = await decryptSecret(hashData, passphrase);
|
|
1071
|
+
document.getElementById("recv-value").textContent = secret;
|
|
1072
|
+
resultBox.style.display = "block";
|
|
1073
|
+
} catch (e) {
|
|
1074
|
+
errEl.textContent = "Decryption failed. Wrong passphrase?";
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
// Check URL hash for incoming encrypted data (link-based receive flow)
|
|
1079
|
+
if (location.hash.startsWith("#QRSEC:")) {
|
|
1080
|
+
// Switch to receive tab
|
|
1081
|
+
document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
|
|
1082
|
+
document.querySelectorAll(".panel").forEach(p => p.classList.remove("active"));
|
|
1083
|
+
document.querySelector('[data-tab="receive"]').classList.add("active");
|
|
1084
|
+
document.getElementById("receive").classList.add("active");
|
|
1085
|
+
// Show decrypt button instead of camera controls
|
|
1086
|
+
document.getElementById("scan-btn").style.display = "none";
|
|
1087
|
+
document.getElementById("decrypt-btn").style.display = "inline-block";
|
|
1088
|
+
document.getElementById("link-notice").style.display = "block";
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1032
1091
|
// Copy to clipboard
|
|
1033
1092
|
document.getElementById("copy-btn").addEventListener("click", async () => {
|
|
1034
1093
|
const value = document.getElementById("recv-value").textContent;
|