diff --git a/README.md b/README.md index e68a0b8..beb1331 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -#### Created by Claude Opus 4.6 +#### Created by Agents NPM link: https://www.npmjs.com/package/vite-plugin-component-locker \ No newline at end of file diff --git a/core/package-lock.json b/core/package-lock.json index 3f7eac1..528988e 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "vite-plugin-component-locker", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vite-plugin-component-locker", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "crypto-js": "^4.2.0", @@ -15,6 +15,7 @@ "devDependencies": { "@types/crypto-js": "^4.2.2", "@types/mime-types": "^3.0.1", + "@types/node": "^25.9.2", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.0", "@vitejs/plugin-react": "^4.5.2", @@ -1226,6 +1227,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, "node_modules/@types/react": { "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", @@ -1768,6 +1780,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", diff --git a/core/package.json b/core/package.json index 297f6d7..aa5df02 100644 --- a/core/package.json +++ b/core/package.json @@ -10,16 +10,21 @@ "security" ], "name": "vite-plugin-component-locker", - "version": "1.0.0", + "version": "1.0.3", "type": "module", "main": "./dist/crypto-locker.umd.cjs", "module": "./dist/crypto-locker.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/crypto-locker.js", "require": "./dist/crypto-locker.umd.cjs" }, - "./plugin": "./src/plugin.ts" + "./plugin": { + "types": "./dist/plugin.d.ts", + "default": "./src/plugin.ts" + } }, "files": [ "dist", @@ -36,6 +41,7 @@ "devDependencies": { "@types/crypto-js": "^4.2.2", "@types/mime-types": "^3.0.1", + "@types/node": "^25.9.2", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.0", "@vitejs/plugin-react": "^4.5.2", @@ -45,6 +51,6 @@ "vite": "^6.3.5" }, "scripts": { - "build": "vite build" + "build": "vite build && tsc --emitDeclarationOnly" } -} +} \ No newline at end of file diff --git a/core/src/plugin.ts b/core/src/plugin.ts index 51a0fe9..a188196 100644 --- a/core/src/plugin.ts +++ b/core/src/plugin.ts @@ -163,17 +163,53 @@ export const mimeType = ${JSON.stringify(mimeType)}; const isAsset = !/\.[jt]sx?$/i.test(cleanId); if (isAsset) return null; // Handled in load() - const { transform: esbuildTransform } = await import("esbuild"); + const { build: esbuildBuild } = await import("esbuild"); - const { code: jsCode } = await esbuildTransform(code, { - loader: cleanId.match(/\.tsx$/i) ? "tsx" : "ts", + const buildResult = await esbuildBuild({ + entryPoints: [cleanId], + bundle: true, format: "cjs", target: "es2020", + write: false, + outdir: "out", jsx: "transform", jsxFactory: "React.createElement", - jsxFragment: "React.Fragment" + jsxFragment: "React.Fragment", + loader: { + ".svg": "dataurl", + ".png": "dataurl", + ".jpg": "dataurl", + ".jpeg": "dataurl", + ".gif": "dataurl", + ".pdf": "dataurl", + }, + // Exclude React and user-specified external dependencies + external: [ + "react", + "react-dom", + ...(options.external || []), + ], }); + const jsFile = buildResult.outputFiles.find(f => f.path.endsWith('.js')); + const cssFile = buildResult.outputFiles.find(f => f.path.endsWith('.css')); + + let jsCode = jsFile ? jsFile.text : ""; + + // Inject bundled CSS into the JS code so it applies at runtime + if (cssFile && cssFile.text.trim()) { + const escapedCss = JSON.stringify(cssFile.text); + const injectCss = ` + if (typeof document !== 'undefined') { + const style = document.createElement('style'); + style.setAttribute('data-crypto-locker', 'true'); + style.textContent = ${escapedCss}; + document.head.appendChild(style); + } + `; + jsCode = injectCss + '\n' + jsCode; + } + const encrypted = cryptoJs.AES.encrypt(jsCode, password).toString(); return { diff --git a/core/src/types.ts b/core/src/types.ts index 141537d..37e4404 100644 --- a/core/src/types.ts +++ b/core/src/types.ts @@ -24,4 +24,7 @@ export interface CryptoLockerPluginOptions { * @default /\.secret\.[jt]sx?$/i */ include?: string | RegExp; + + /** Additional external dependencies to exclude from bundling */ + external?: string[]; } diff --git a/core/src/useCryptoAsset.ts b/core/src/useCryptoAsset.ts index 39e3c7a..c2d11b6 100644 --- a/core/src/useCryptoAsset.ts +++ b/core/src/useCryptoAsset.ts @@ -81,7 +81,7 @@ export function useCryptoAsset() { // Convert base64 back to Blob const uint8Array = base64ToUint8Array(plaintextBase64); - const blob = new Blob([uint8Array], { type: mod.mimeType || "application/octet-stream" }); + const blob = new Blob([uint8Array as any], { type: mod.mimeType || "application/octet-stream" }); const objectUrl = URL.createObjectURL(blob); setDecryptedUrl(objectUrl); diff --git a/core/src/useCryptoLocker.ts b/core/src/useCryptoLocker.ts index 26d537e..aca70e0 100644 --- a/core/src/useCryptoLocker.ts +++ b/core/src/useCryptoLocker.ts @@ -1,6 +1,7 @@ import { useState, useCallback } from "react"; import CryptoJS from "crypto-js"; -import React from "react"; +import * as React from "react"; +import * as jsxRuntime from "react/jsx-runtime"; import type { CryptoLockerModule } from "./types"; export type CryptoLockerStatus = "idle" | "loading" | "success" | "error"; @@ -8,11 +9,13 @@ export type CryptoLockerStatus = "idle" | "loading" | "success" | "error"; /** * Hook that provides logic for AES decryption of dynamic modules. * UI, password handling, and validation are fully delegated to the developer. + * + * @param dependencies Optional dictionary of external dependencies to provide to the encrypted module. */ -export function useCryptoLocker() { - const [status, setStatus] = useState("idle"); - const [decryptedData, setDecryptedData] = useState(null); - const [error, setError] = useState(null); +export function useCryptoLocker(dependencies: Record = {}) { + const [status, setStatus] = React.useState("idle"); + const [decryptedData, setDecryptedData] = React.useState(null); + const [error, setError] = React.useState(null); /** * Decrypts the dynamically loaded module and evaluates its code. @@ -53,9 +56,13 @@ export function useCryptoLocker() { const exportsObj: any = {}; const moduleObj = { exports: exportsObj }; - // Provide React dependency to the evaluated module + // Provide dependencies to the evaluated module const requireFn = (id: string) => { if (id === "react") return React; + if (id === "react/jsx-runtime") return jsxRuntime; + if (id === "react-dom") return (window as any).ReactDOM || {}; + if (/\.(css|scss|less)$/i.test(id)) return {}; // Ignore CSS imports inside secret modules + if (id in dependencies) return dependencies[id]; throw new Error(`Cannot require '${id}' in encrypted module.`); };