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.
Files changed (2) hide show
  1. package/index.html +64 -5
  2. 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.4.1</p>
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 &mdash; 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 ~2000 characters.";
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
- if (!decodedText.startsWith("QRSEC:")) {
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(decodedText.slice(6), passphrase);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qr-secure-send",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Encrypt and transfer secrets via QR code",
5
5
  "keywords": ["qr", "qrcode", "encryption", "password", "secure", "transfer"],
6
6
  "author": "",