feat: extend crypto-locker plugin to support automatic AES encryption for static assets via new useCryptoAsset hook
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
|
||||||
|
<!-- Removed the solid background rect so the SVG is transparent -->
|
||||||
|
<rect x="60" y="90" width="80" height="60" fill="#cbd5e0" rx="10"/>
|
||||||
|
<path d="M70 90 V70 A30 30 0 0 1 130 70 V90" fill="none" stroke="#cbd5e0" stroke-width="15" stroke-linecap="round"/>
|
||||||
|
<circle cx="100" cy="120" r="10" fill="#4a5568"/>
|
||||||
|
<path d="M97 120 L95 135 H105 L103 120" fill="#4a5568"/>
|
||||||
|
<text x="100" y="180" font-family="sans-serif" font-size="14" fill="#a0aec0" text-anchor="middle">TOP SECRET</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 584 B |
+70
-14
@@ -1,30 +1,86 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useCryptoLocker } from "vite-plugin-component-locker";
|
import { useCryptoLocker, useCryptoAsset } from "vite-plugin-component-locker";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
// The decrypted data will be a React component type
|
// The decrypted data will be a React component type
|
||||||
type SecretComponentType = React.ComponentType;
|
type SecretComponentType = React.ComponentType;
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { unlock, status, decryptedData: SecretComponent, error } = useCryptoLocker<SecretComponentType>();
|
const { unlock: unlockComponent, status: compStatus, decryptedData: SecretComponent, error: compError } = useCryptoLocker<SecretComponentType>();
|
||||||
|
const { unlock: unlockAsset, status: assetStatus, decryptedUrl: secretImageUrl, error: assetError } = useCryptoAsset();
|
||||||
|
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
const [isInitializing, setIsInitializing] = useState(true);
|
||||||
|
|
||||||
|
// Auto-unlock if password is in sessionStorage
|
||||||
|
useEffect(() => {
|
||||||
|
const savedPassword = sessionStorage.getItem("locker_password");
|
||||||
|
if (savedPassword) {
|
||||||
|
setPassword(savedPassword);
|
||||||
|
handleUnlock(savedPassword);
|
||||||
|
} else {
|
||||||
|
setIsInitializing(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUnlock = async (pwd: string) => {
|
||||||
|
try {
|
||||||
|
// Unlock both component and asset simultaneously
|
||||||
|
await Promise.all([
|
||||||
|
unlockComponent(() => import("./secret-content.secret"), pwd),
|
||||||
|
unlockAsset("/secret-image.secret.svg", pwd)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Save password to session storage so it survives page reloads
|
||||||
|
sessionStorage.setItem("locker_password", pwd);
|
||||||
|
} catch {
|
||||||
|
// Errors are already caught and managed by the hooks
|
||||||
|
sessionStorage.removeItem("locker_password");
|
||||||
|
} finally {
|
||||||
|
setIsInitializing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!password.trim()) return;
|
if (!password.trim()) return;
|
||||||
|
await handleUnlock(password);
|
||||||
try {
|
|
||||||
await unlock(() => import("./secret-content.secret"), password);
|
|
||||||
} catch {
|
|
||||||
// The error is already caught and managed by useCryptoLocker's state.
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLock = () => {
|
||||||
|
sessionStorage.removeItem("locker_password");
|
||||||
|
window.location.reload(); // Quick way to reset all states
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isInitializing) {
|
||||||
|
return <div className="app">Loading session...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUnlocked = compStatus === "success" && assetStatus === "success";
|
||||||
|
const error = compError || assetError;
|
||||||
|
const isLoading = compStatus === "loading" || assetStatus === "loading";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<div className="app__container">
|
<div className="app__container">
|
||||||
{status === "success" && SecretComponent ? (
|
{isUnlocked && SecretComponent ? (
|
||||||
<SecretComponent />
|
<div style={{ display: "flex", flexDirection: "column", gap: "1rem", alignItems: "center" }}>
|
||||||
|
<SecretComponent />
|
||||||
|
|
||||||
|
{secretImageUrl && (
|
||||||
|
<div style={{ marginTop: "1rem", padding: "1rem", border: "1px dashed #ccc", borderRadius: "8px" }}>
|
||||||
|
<p style={{ marginBottom: "0.5rem", color: "#555" }}>Decrypted Static Asset:</p>
|
||||||
|
<img src={secretImageUrl} alt="Secret" style={{ display: "block", maxWidth: "200px" }} />
|
||||||
|
<a href={secretImageUrl} download="secret.svg" style={{ display: "block", marginTop: "0.5rem", color: "#0066cc" }}>
|
||||||
|
Download Image
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button onClick={handleLock} style={{ marginTop: "2rem", padding: "0.5rem 1rem", background: "#eee", border: "1px solid #ccc", cursor: "pointer" }}>
|
||||||
|
Lock Again
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: "1rem", maxWidth: "300px", margin: "0 auto" }}>
|
<form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: "1rem", maxWidth: "300px", margin: "0 auto" }}>
|
||||||
<h2>Enter Password</h2>
|
<h2>Enter Password</h2>
|
||||||
@@ -34,7 +90,7 @@ export default function App() {
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Enter password…"
|
placeholder="Enter password…"
|
||||||
disabled={status === "loading"}
|
disabled={isLoading}
|
||||||
style={{ padding: "0.5rem", fontSize: "1rem" }}
|
style={{ padding: "0.5rem", fontSize: "1rem" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -42,10 +98,10 @@ export default function App() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={status === "loading" || !password.trim()}
|
disabled={isLoading || !password.trim()}
|
||||||
style={{ padding: "0.5rem 1rem", fontSize: "1rem", cursor: "pointer" }}
|
style={{ padding: "0.5rem 1rem", fontSize: "1rem", cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
{status === "loading" ? "Loading…" : "Unlock"}
|
{isLoading ? "Loading…" : "Unlock"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user