qr-secure-send 1.6.7 → 1.7.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 +87 -43
- package/package.json +1 -1
package/index.html
CHANGED
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
<header>
|
|
160
160
|
<h1>QR Secure Send</h1>
|
|
161
161
|
<p>Encrypt and transfer secrets via QR code</p>
|
|
162
|
-
<p style="font-size:0.7rem;color:#484f58;margin-top:0.2rem">v1.
|
|
162
|
+
<p style="font-size:0.7rem;color:#484f58;margin-top:0.2rem">v1.7.1</p>
|
|
163
163
|
</header>
|
|
164
164
|
|
|
165
165
|
<div class="tabs">
|
|
@@ -186,15 +186,14 @@
|
|
|
186
186
|
<div class="wallet-section">
|
|
187
187
|
<label class="wallet-toggle">
|
|
188
188
|
<input type="checkbox" id="send-wallet-toggle">
|
|
189
|
-
|
|
189
|
+
Encrypt with crypto wallet (MetaMask)
|
|
190
190
|
</label>
|
|
191
191
|
<div id="send-wallet-fields" class="wallet-fields">
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
style="font-family:'SF Mono','Fira Code',monospace;font-size:0.85rem;">
|
|
195
|
-
<p class="info" style="margin-top:-0.5rem;text-align:left">
|
|
196
|
-
The receiver must connect this exact wallet to decrypt. The address is mixed into the encryption key.
|
|
192
|
+
<p class="info" style="text-align:left;margin-bottom:0.5rem">
|
|
193
|
+
Your wallet's private key will be used to encrypt. Only the same wallet can decrypt.
|
|
197
194
|
</p>
|
|
195
|
+
<button class="wallet-btn" id="send-wallet-connect-btn">Connect MetaMask</button>
|
|
196
|
+
<div id="send-wallet-status" class="wallet-address" style="display:none"></div>
|
|
198
197
|
</div>
|
|
199
198
|
</div>
|
|
200
199
|
|
|
@@ -223,9 +222,9 @@
|
|
|
223
222
|
</div>
|
|
224
223
|
<div id="recv-wallet-section" class="wallet-section" style="display:none">
|
|
225
224
|
<p style="font-size:0.82rem;color:#d29922;margin-bottom:0.5rem;">
|
|
226
|
-
This secret
|
|
225
|
+
This secret was encrypted with a wallet. Connect the same wallet to decrypt.
|
|
227
226
|
</p>
|
|
228
|
-
<button class="wallet-btn" id="recv-wallet-connect-btn">Connect MetaMask</button>
|
|
227
|
+
<button class="wallet-btn" id="recv-wallet-connect-btn">Connect & Sign with MetaMask</button>
|
|
229
228
|
<a class="wallet-btn" id="recv-wallet-deeplink" style="display:none;text-decoration:none;text-align:center" target="_blank">Open in MetaMask App</a>
|
|
230
229
|
<div id="recv-wallet-status" class="wallet-address" style="display:none"></div>
|
|
231
230
|
</div>
|
|
@@ -1017,12 +1016,24 @@
|
|
|
1017
1016
|
// PAYLOAD HELPERS
|
|
1018
1017
|
// =========================================================================
|
|
1019
1018
|
|
|
1020
|
-
const APP_VERSION = "1.
|
|
1019
|
+
const APP_VERSION = "1.7.1";
|
|
1021
1020
|
const GH_PAGES_URL = "https://degenddy.github.io/qr-secure-send/?v=" + APP_VERSION;
|
|
1022
1021
|
const METAMASK_DEEP_URL = "https://link.metamask.io/dapp/degenddy.github.io/qr-secure-send/?v=" + APP_VERSION;
|
|
1023
1022
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1023
|
+
const WALLET_SIGN_MSG = "QR Secure Send: generate encryption key";
|
|
1024
|
+
|
|
1025
|
+
// Ask MetaMask to sign a deterministic message. Returns the signature (hex string).
|
|
1026
|
+
// Same wallet + same message = same signature every time (RFC 6979).
|
|
1027
|
+
async function getWalletSignature(account) {
|
|
1028
|
+
const sig = await window.ethereum.request({
|
|
1029
|
+
method: 'personal_sign',
|
|
1030
|
+
params: [WALLET_SIGN_MSG, account]
|
|
1031
|
+
});
|
|
1032
|
+
return sig;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function buildKeyInput(passphrase, walletSignature) {
|
|
1036
|
+
return walletSignature ? passphrase + ':' + walletSignature : passphrase;
|
|
1026
1037
|
}
|
|
1027
1038
|
|
|
1028
1039
|
// Extract encrypted data from raw or URL form. Returns { data, wallet } or null.
|
|
@@ -1077,12 +1088,35 @@
|
|
|
1077
1088
|
document.getElementById("send-wallet-fields").classList.toggle("active", e.target.checked);
|
|
1078
1089
|
});
|
|
1079
1090
|
|
|
1091
|
+
// Send wallet connect
|
|
1092
|
+
let sendWalletSignature = null;
|
|
1093
|
+
|
|
1094
|
+
document.getElementById("send-wallet-connect-btn").addEventListener("click", async () => {
|
|
1095
|
+
const errEl = document.getElementById("send-error");
|
|
1096
|
+
const statusEl = document.getElementById("send-wallet-status");
|
|
1097
|
+
if (typeof window.ethereum === 'undefined') {
|
|
1098
|
+
errEl.textContent = "MetaMask not detected. Install the extension or use MetaMask mobile.";
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
1103
|
+
if (!accounts.length) { errEl.textContent = "No accounts returned."; return; }
|
|
1104
|
+
const account = accounts[0];
|
|
1105
|
+
sendWalletSignature = await getWalletSignature(account);
|
|
1106
|
+
statusEl.textContent = "Signed with: " + account.toLowerCase();
|
|
1107
|
+
statusEl.style.display = "block";
|
|
1108
|
+
document.getElementById("send-wallet-connect-btn").textContent = "Wallet Connected";
|
|
1109
|
+
document.getElementById("send-wallet-connect-btn").disabled = true;
|
|
1110
|
+
} catch (e) {
|
|
1111
|
+
errEl.textContent = "Wallet connection/signing failed: " + (e.message || "User rejected.");
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1080
1115
|
// QR Generation
|
|
1081
1116
|
document.getElementById("generate-btn").addEventListener("click", async () => {
|
|
1082
1117
|
const secret = document.getElementById("secret").value.trim();
|
|
1083
1118
|
const passphrase = document.getElementById("send-passphrase").value;
|
|
1084
1119
|
const useWallet = document.getElementById("send-wallet-toggle").checked;
|
|
1085
|
-
const walletAddress = useWallet ? document.getElementById("send-wallet-address").value.trim() : null;
|
|
1086
1120
|
const errEl = document.getElementById("send-error");
|
|
1087
1121
|
const output = document.getElementById("qr-output");
|
|
1088
1122
|
const info = document.getElementById("qr-info");
|
|
@@ -1103,15 +1137,13 @@
|
|
|
1103
1137
|
}
|
|
1104
1138
|
}
|
|
1105
1139
|
|
|
1106
|
-
if (useWallet) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
return;
|
|
1110
|
-
}
|
|
1140
|
+
if (useWallet && !sendWalletSignature) {
|
|
1141
|
+
errEl.textContent = "Connect and sign with MetaMask first.";
|
|
1142
|
+
return;
|
|
1111
1143
|
}
|
|
1112
1144
|
|
|
1113
1145
|
try {
|
|
1114
|
-
const keyInput = buildKeyInput(passphrase,
|
|
1146
|
+
const keyInput = buildKeyInput(passphrase, useWallet ? sendWalletSignature : null);
|
|
1115
1147
|
const encrypted = await encryptSecret(secret, keyInput);
|
|
1116
1148
|
const prefix = useWallet ? "QRSECW:" : "QRSEC:";
|
|
1117
1149
|
const payload = GH_PAGES_URL + "#" + prefix + encrypted;
|
|
@@ -1137,14 +1169,14 @@
|
|
|
1137
1169
|
"loaded from the internet when you start the camera.";
|
|
1138
1170
|
}
|
|
1139
1171
|
|
|
1140
|
-
// Wallet connect (receive side)
|
|
1141
|
-
let
|
|
1172
|
+
// Wallet connect & sign (receive side)
|
|
1173
|
+
let recvWalletSignature = null;
|
|
1142
1174
|
|
|
1143
1175
|
document.getElementById("recv-wallet-connect-btn").addEventListener("click", async () => {
|
|
1144
1176
|
const statusEl = document.getElementById("recv-wallet-status");
|
|
1145
1177
|
const errEl = document.getElementById("recv-error");
|
|
1146
1178
|
if (typeof window.ethereum === 'undefined') {
|
|
1147
|
-
errEl.textContent = "MetaMask not detected. Install the
|
|
1179
|
+
errEl.textContent = "MetaMask not detected. Install the extension, or use the button below to open in the MetaMask mobile app.";
|
|
1148
1180
|
const deepEl = document.getElementById("recv-wallet-deeplink");
|
|
1149
1181
|
deepEl.href = METAMASK_DEEP_URL + location.hash;
|
|
1150
1182
|
deepEl.style.display = "inline-block";
|
|
@@ -1152,30 +1184,33 @@
|
|
|
1152
1184
|
}
|
|
1153
1185
|
try {
|
|
1154
1186
|
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
1155
|
-
if (!accounts.length) { errEl.textContent = "No accounts returned.
|
|
1156
|
-
|
|
1157
|
-
|
|
1187
|
+
if (!accounts.length) { errEl.textContent = "No accounts returned."; return; }
|
|
1188
|
+
const account = accounts[0];
|
|
1189
|
+
recvWalletSignature = await getWalletSignature(account);
|
|
1190
|
+
statusEl.textContent = "Signed with: " + account.toLowerCase();
|
|
1158
1191
|
statusEl.style.display = "block";
|
|
1159
|
-
document.getElementById("recv-wallet-connect-btn").textContent = "Wallet
|
|
1192
|
+
document.getElementById("recv-wallet-connect-btn").textContent = "Wallet Signed";
|
|
1160
1193
|
document.getElementById("recv-wallet-connect-btn").disabled = true;
|
|
1161
1194
|
} catch (e) {
|
|
1162
|
-
errEl.textContent = "Wallet connection failed: " + (e.message || "User rejected
|
|
1195
|
+
errEl.textContent = "Wallet connection/signing failed: " + (e.message || "User rejected.");
|
|
1163
1196
|
}
|
|
1164
1197
|
});
|
|
1165
1198
|
|
|
1166
|
-
//
|
|
1199
|
+
// Re-sign if user switches account in MetaMask
|
|
1167
1200
|
if (typeof window.ethereum !== 'undefined') {
|
|
1168
|
-
window.ethereum.on('accountsChanged', (accounts) => {
|
|
1201
|
+
window.ethereum.on('accountsChanged', async (accounts) => {
|
|
1169
1202
|
if (accounts.length) {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1203
|
+
try {
|
|
1204
|
+
recvWalletSignature = await getWalletSignature(accounts[0]);
|
|
1205
|
+
const statusEl = document.getElementById("recv-wallet-status");
|
|
1206
|
+
statusEl.textContent = "Signed with: " + accounts[0].toLowerCase();
|
|
1207
|
+
statusEl.style.display = "block";
|
|
1208
|
+
} catch (_) { recvWalletSignature = null; }
|
|
1174
1209
|
} else {
|
|
1175
|
-
|
|
1210
|
+
recvWalletSignature = null;
|
|
1176
1211
|
document.getElementById("recv-wallet-status").style.display = "none";
|
|
1177
1212
|
const btn = document.getElementById("recv-wallet-connect-btn");
|
|
1178
|
-
btn.textContent = "Connect MetaMask"; btn.disabled = false;
|
|
1213
|
+
btn.textContent = "Connect & Sign with MetaMask"; btn.disabled = false;
|
|
1179
1214
|
}
|
|
1180
1215
|
});
|
|
1181
1216
|
}
|
|
@@ -1205,13 +1240,13 @@
|
|
|
1205
1240
|
}
|
|
1206
1241
|
if (payload.wallet) {
|
|
1207
1242
|
document.getElementById("recv-wallet-section").style.display = "block";
|
|
1208
|
-
if (!
|
|
1209
|
-
errEl.textContent = "This secret requires wallet
|
|
1243
|
+
if (!recvWalletSignature) {
|
|
1244
|
+
errEl.textContent = "This secret requires wallet signing. Connect & sign with MetaMask first, then scan again.";
|
|
1210
1245
|
return false;
|
|
1211
1246
|
}
|
|
1212
1247
|
}
|
|
1213
1248
|
try {
|
|
1214
|
-
const keyInput = buildKeyInput(passphrase, payload.wallet ?
|
|
1249
|
+
const keyInput = buildKeyInput(passphrase, payload.wallet ? recvWalletSignature : null);
|
|
1215
1250
|
const secret = await decryptSecret(payload.data, keyInput);
|
|
1216
1251
|
document.getElementById("recv-value").textContent = secret;
|
|
1217
1252
|
resultBox.style.display = "block";
|
|
@@ -1258,14 +1293,14 @@
|
|
|
1258
1293
|
const payload = extractPayload(location.hash.slice(1));
|
|
1259
1294
|
if (!payload) { errEl.textContent = "No encrypted data found in URL."; return; }
|
|
1260
1295
|
|
|
1261
|
-
if (payload.wallet && !
|
|
1262
|
-
errEl.textContent = "This secret requires wallet
|
|
1296
|
+
if (payload.wallet && !recvWalletSignature) {
|
|
1297
|
+
errEl.textContent = "This secret requires wallet signing. Connect & sign with MetaMask first.";
|
|
1263
1298
|
document.getElementById("recv-wallet-section").style.display = "block";
|
|
1264
1299
|
return;
|
|
1265
1300
|
}
|
|
1266
1301
|
|
|
1267
1302
|
try {
|
|
1268
|
-
const keyInput = buildKeyInput(passphrase, payload.wallet ?
|
|
1303
|
+
const keyInput = buildKeyInput(passphrase, payload.wallet ? recvWalletSignature : null);
|
|
1269
1304
|
const secret = await decryptSecret(payload.data, keyInput);
|
|
1270
1305
|
document.getElementById("recv-value").textContent = secret;
|
|
1271
1306
|
resultBox.style.display = "block";
|
|
@@ -1278,6 +1313,15 @@
|
|
|
1278
1313
|
|
|
1279
1314
|
// Check URL hash for incoming encrypted data (link-based receive flow)
|
|
1280
1315
|
if (location.hash.startsWith("#QRSEC:") || location.hash.startsWith("#QRSECW:")) {
|
|
1316
|
+
// Wallet mode + no MetaMask → redirect to MetaMask deep link (mobile)
|
|
1317
|
+
if (location.hash.startsWith("#QRSECW:") && typeof window.ethereum === 'undefined') {
|
|
1318
|
+
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
1319
|
+
if (isMobile) {
|
|
1320
|
+
window.location.href = METAMASK_DEEP_URL + location.hash;
|
|
1321
|
+
// stop further execution; page will redirect
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1281
1325
|
document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
|
|
1282
1326
|
document.querySelectorAll(".panel").forEach(p => p.classList.remove("active"));
|
|
1283
1327
|
document.querySelector('[data-tab="receive"]').classList.add("active");
|
|
@@ -1288,8 +1332,8 @@
|
|
|
1288
1332
|
if (location.hash.startsWith("#QRSECW:")) {
|
|
1289
1333
|
document.getElementById("recv-wallet-section").style.display = "block";
|
|
1290
1334
|
document.getElementById("link-notice").textContent =
|
|
1291
|
-
"Encrypted data received via link. This secret requires wallet
|
|
1292
|
-
"Connect your wallet, enter the passphrase, and click Decrypt.";
|
|
1335
|
+
"Encrypted data received via link. This secret requires wallet signing. " +
|
|
1336
|
+
"Connect & sign with your wallet, enter the passphrase (if set), and click Decrypt.";
|
|
1293
1337
|
}
|
|
1294
1338
|
}
|
|
1295
1339
|
|