- Add Import pk from camera: scan QR → restore pb → auto login → refresh - Add scanner.js (jsQR) for camera QR decode - QR visibility: pk shown by default, pb hidden by default (toggles) - Update docs/frontend-development.md with scanner, Import pk, QR behavior Made-with: Cursor
This commit is contained in:
+66
-1
@@ -1,5 +1,6 @@
|
||||
import { createApiClient } from "./api.js";
|
||||
import { toDataURL } from "./qr.js";
|
||||
import { scanQRFromCamera } from "./scanner.js";
|
||||
|
||||
const { createApp, ref, reactive, onMounted, watch } = Vue;
|
||||
|
||||
@@ -21,7 +22,11 @@ createApp({
|
||||
status: "Ready",
|
||||
qrPk: "",
|
||||
qrPb: "",
|
||||
showPrivateQR: false,
|
||||
showPrivateQR: true,
|
||||
showPublicQR: false,
|
||||
showCameraDialog: false,
|
||||
cameraError: "",
|
||||
cameraAbortController: null,
|
||||
});
|
||||
|
||||
let client = createApiClient(apiBase.value);
|
||||
@@ -79,6 +84,59 @@ createApp({
|
||||
}
|
||||
};
|
||||
|
||||
const closeCameraDialog = () => {
|
||||
state.showCameraDialog = false;
|
||||
if (state.cameraAbortController) {
|
||||
state.cameraAbortController.abort();
|
||||
state.cameraAbortController = null;
|
||||
}
|
||||
state.cameraError = "";
|
||||
};
|
||||
|
||||
const runCameraScan = async () => {
|
||||
state.cameraError = "";
|
||||
state.cameraAbortController = new AbortController();
|
||||
const videoEl = document.getElementById("camera-video");
|
||||
if (!videoEl) {
|
||||
state.cameraError = "Video element not found.";
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const pk = await scanQRFromCamera(videoEl, state.cameraAbortController.signal);
|
||||
const pkTrimmed = pk.trim();
|
||||
closeCameraDialog();
|
||||
state.privateKey = pkTrimmed;
|
||||
const pb = await client.derivePublicKey(pkTrimmed);
|
||||
state.publicKey = pb;
|
||||
client.importKeys({ publicKey: pb, privateKey: pkTrimmed });
|
||||
await refreshQRCodes();
|
||||
try {
|
||||
try {
|
||||
await client.registerBySigningServiceKey(pb, pkTrimmed);
|
||||
} catch (err) {
|
||||
const msg = (err?.message || "").toLowerCase();
|
||||
const ignore = msg.includes("already registered") || msg.includes("not configured") ||
|
||||
msg.includes("admin_public_key") || msg.includes("409") || msg.includes("conflict");
|
||||
if (!ignore) throw err;
|
||||
}
|
||||
state.accessToken = await client.loginWithSignature(pb, pkTrimmed);
|
||||
client.setAccessToken(state.accessToken);
|
||||
await listCollections();
|
||||
state.status = "Imported pk from camera, restored pb, logged in.";
|
||||
} catch (err) {
|
||||
state.status = `Keys imported. Login failed: ${err.message}`;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name === "AbortError") return;
|
||||
state.cameraError = err.message || "Camera access failed.";
|
||||
}
|
||||
};
|
||||
|
||||
const importPkFromCamera = () => {
|
||||
state.showCameraDialog = true;
|
||||
Vue.nextTick(() => runCameraScan());
|
||||
};
|
||||
|
||||
const register = async () => {
|
||||
try {
|
||||
await client.registerBySigningServiceKey(state.publicKey, state.privateKey);
|
||||
@@ -254,12 +312,18 @@ createApp({
|
||||
state.showPrivateQR = !state.showPrivateQR;
|
||||
};
|
||||
|
||||
const togglePublicQR = () => {
|
||||
state.showPublicQR = !state.showPublicQR;
|
||||
};
|
||||
|
||||
return {
|
||||
apiBase,
|
||||
state,
|
||||
rebuildClient,
|
||||
ensureKeys,
|
||||
restorePublicKeyFromPrivate,
|
||||
importPkFromCamera,
|
||||
closeCameraDialog,
|
||||
register,
|
||||
login,
|
||||
listCollections,
|
||||
@@ -275,6 +339,7 @@ createApp({
|
||||
formatFeature,
|
||||
featuresFor,
|
||||
togglePrivateQR,
|
||||
togglePublicQR,
|
||||
};
|
||||
},
|
||||
}).use(Vuetify.createVuetify()).mount("#app");
|
||||
|
||||
Reference in New Issue
Block a user