├── .gitignore ├── example ├── demo-privacy.gif ├── v-privacy-logo.png └── demo-privacy-encrypt.gif ├── CHANGELOG.md ├── tsconfig.json ├── source ├── interfaces.ts ├── index.d.ts ├── index.ts └── VPrivacy.ts ├── rollup.config.js ├── LICENSE ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /example/demo-privacy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fchancel/v-privacy/HEAD/example/demo-privacy.gif -------------------------------------------------------------------------------- /example/v-privacy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fchancel/v-privacy/HEAD/example/v-privacy-logo.png -------------------------------------------------------------------------------- /example/demo-privacy-encrypt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fchancel/v-privacy/HEAD/example/demo-privacy-encrypt.gif -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.1.0] - 2023-04-22 4 | ### Added 5 | - Added the feature to encrypt text nodes with the "encryptText" and "secretKey" options. 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "sourceMap": true, 8 | "outDir": "dist" 9 | }, 10 | "include": ["source/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /source/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface VPrivacyOptions { 2 | blur?: number; 3 | blurColor?: string; 4 | disabledAction?: boolean; 5 | disabledScreenReader?: boolean; 6 | isPrivate?: boolean; 7 | isSelectable?: boolean; 8 | isTabbable?: boolean; 9 | transitionDelay?: number; 10 | transitionDuration?: number; 11 | transitionTimingFunction?: string; 12 | encryptText?: boolean; 13 | secretKey?: string; 14 | } 15 | -------------------------------------------------------------------------------- /source/index.d.ts: -------------------------------------------------------------------------------- 1 | import { DirectiveBinding } from "vue"; 2 | import type { VPrivacyOptions } from "./interfaces"; 3 | 4 | export declare const VPrivacyDirective: (options: VPrivacyOptions) => { 5 | mounted(el: HTMLElement, binding: DirectiveBinding): void; 6 | updated(el: HTMLElement, binding: DirectiveBinding): void; 7 | }; 8 | 9 | export declare const VPrivacy: { 10 | install(app: any, options?: VPrivacyOptions): void; 11 | }; 12 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import terser from '@rollup/plugin-terser'; 2 | import typescript from "rollup-plugin-typescript2"; 3 | import { defineConfig } from "rollup"; 4 | 5 | export default defineConfig({ 6 | input: "source/index.ts", 7 | output: [ 8 | { 9 | file: "dist/index.js", 10 | format: "cjs", 11 | exports: "named", 12 | sourcemap: true, 13 | }, 14 | { 15 | file: "dist/index.min.js", 16 | format: "cjs", 17 | exports: "named", 18 | plugins: [terser()], 19 | sourcemap: true, 20 | }, 21 | { 22 | file: "dist/index.esm.js", 23 | format: "esm", 24 | exports: "named", 25 | sourcemap: true, 26 | }, 27 | ], 28 | plugins: [typescript()], 29 | external: ["vue"], 30 | }); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 fchancel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-privacy", 3 | "version": "1.1.0", 4 | "description": "A vue3 plugin that allows to manage the privacy of an HTML element by blurring its content and encrypt node text data in DOM", 5 | "author": "Florian CHANCELADE ", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/fchancel/v-privacy" 12 | }, 13 | "scripts": { 14 | "build": "rollup -c" 15 | }, 16 | "keywords": [ 17 | "vue", 18 | "privacy", 19 | "directive", 20 | "blur" 21 | ], 22 | "bugs": { 23 | "url": "https://github.com/fchancel/v-privacy/issues" 24 | }, 25 | "homepage": "https://github.com/fchancel/v-privacy#readme", 26 | "peerDependencies": { 27 | "vue": "^3.0.0" 28 | }, 29 | "dependencies": { 30 | "crypto-js": "^4.1.1" 31 | }, 32 | "devDependencies": { 33 | "@rollup/plugin-terser": "0.4.1", 34 | "rollup": "^2.0.0", 35 | "rollup-plugin-typescript2": "^0.34.1", 36 | "typescript": "5.0.4" 37 | }, 38 | "files": [ 39 | "dist" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /source/index.ts: -------------------------------------------------------------------------------- 1 | import type { createApp } from "vue"; 2 | import { VPrivacyDirective } from "./VPrivacy"; 3 | import type { VPrivacyOptions } from "./interfaces"; 4 | 5 | export const VPrivacy = { 6 | install: ( 7 | app: ReturnType, 8 | options?: VPrivacyOptions 9 | ): void => { 10 | const defaultOptions: VPrivacyOptions = { 11 | blur: options?.blur ?? 5, 12 | isPrivate: false, 13 | isSelectable: false, 14 | disabledAction: true, 15 | disabledScreenReader: true, 16 | isTabbable: false, 17 | transitionDelay: 0, 18 | transitionDuration: 0.2, 19 | transitionTimingFunction: "linear", 20 | encryptText: false, 21 | secretKey: "", 22 | }; 23 | 24 | // Manage option between default options and options given 25 | const finalOptions = { ...defaultOptions, ...options }; 26 | 27 | if ( 28 | finalOptions.encryptText && 29 | (!finalOptions.secretKey || finalOptions.secretKey?.length === 0) 30 | ) { 31 | throw new Error( 32 | "v-privacy - Incorrect options - If encryptText is true, secretKey is mandatory" 33 | ); 34 | } 35 | 36 | app.directive("privacy", VPrivacyDirective(finalOptions)); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /source/VPrivacy.ts: -------------------------------------------------------------------------------- 1 | import type { Directive } from "vue"; 2 | import type { VPrivacyOptions } from "./interfaces"; 3 | import CryptoJS from "crypto-js"; 4 | 5 | export function updateStyle(el: HTMLElement, options: VPrivacyOptions) { 6 | // Blur color effect 7 | el.style.backgroundColor = 8 | options.isPrivate && options.blurColor ? options.blurColor : ""; 9 | 10 | // Blur effect 11 | el.style.filter = `blur(${options.isPrivate ? options.blur : 0}px)`; 12 | 13 | // Deactivate mouse interaction 14 | el.style.pointerEvents = 15 | options.isPrivate && options.disabledAction ? "none" : "auto"; 16 | 17 | // Transition effect 18 | el.style.transitionDuration = `${options.transitionDuration}s`; 19 | el.style.transitionDelay = `${options.transitionDelay}s`; 20 | el.style.transitionTimingFunction = `${options.transitionTimingFunction}`; 21 | el.style.transitionProperty = "all"; 22 | 23 | // Deactivate user interaction 24 | if (!options.isSelectable) { 25 | el.style.userSelect = options.isPrivate ? "none" : "auto"; 26 | } 27 | 28 | // Deactivate screen reader 29 | if (options.isPrivate) { 30 | if (options.disabledScreenReader) { 31 | el.setAttribute("aria-hidden", "true"); 32 | } 33 | } else { 34 | el.removeAttribute("aria-hidden"); 35 | } 36 | 37 | // Deactivate tabulation on element 38 | if (options.isPrivate) { 39 | if (!options.isTabbable || options.disabledAction) { 40 | el.setAttribute("tabindex", "-1"); 41 | } 42 | } else { 43 | el.removeAttribute("tabindex"); 44 | } 45 | } 46 | 47 | function encryptText(text: string, secretKey: string) { 48 | const ciphertext = CryptoJS.AES.encrypt(text, secretKey).toString(); 49 | return ciphertext; 50 | } 51 | 52 | function decryptText(ciphertext: string, secretKey: string) { 53 | const bytes = CryptoJS.AES.decrypt(ciphertext, secretKey); 54 | const plaintext = bytes.toString(CryptoJS.enc.Utf8); 55 | if (!plaintext) { 56 | return ciphertext; 57 | } 58 | return plaintext; 59 | } 60 | 61 | function isTextElement(el: HTMLElement) { 62 | const textTags = [ 63 | "A", 64 | "ABBR", 65 | "ACRONYM", 66 | "B", 67 | "BDO", 68 | "BIG", 69 | "BUTTON", 70 | "CITE", 71 | "CODE", 72 | "DFN", 73 | "EM", 74 | "I", 75 | "KBD", 76 | "LABEL", 77 | "P", 78 | "Q", 79 | "SAMP", 80 | "SMALL", 81 | "SPAN", 82 | "STRONG", 83 | "SUB", 84 | "SUP", 85 | "TIME", 86 | "TT", 87 | "U", 88 | "VAR", 89 | ]; 90 | return textTags.includes(el.tagName); 91 | } 92 | 93 | function manageTextEncrypt(el: HTMLElement, options: VPrivacyOptions) { 94 | // Check if element is a text node 95 | if (options.encryptText && isTextElement(el)) { 96 | 97 | if (options.isPrivate) { 98 | // Encrypt phase 99 | const textLength = el.innerText.length; 100 | const encryptedContent = encryptText(el.innerText, options.secretKey!); 101 | el.setAttribute("data-encrypted-content", encryptedContent); 102 | el.innerText = encryptedContent.slice(0, textLength - 3); 103 | } else { 104 | // Decrypt phase 105 | const encryptedContent = el.getAttribute("data-encrypted-content"); 106 | if (encryptedContent) { 107 | el.innerText = decryptText(encryptedContent, options.secretKey!); 108 | el.removeAttribute("data-encrypted-content"); 109 | } 110 | } 111 | } 112 | } 113 | 114 | export function VPrivacyDirective(options: VPrivacyOptions): Directive { 115 | return { 116 | mounted(el, binding) { 117 | // Manage options 118 | const finalOptions: VPrivacyOptions = 119 | typeof binding.value === "boolean" 120 | ? { ...options, isPrivate: binding.value } 121 | : { ...options, ...binding.value }; 122 | 123 | updateStyle(el, finalOptions); 124 | manageTextEncrypt(el, finalOptions); 125 | }, 126 | 127 | updated(el, binding) { 128 | // Manage options 129 | const finalOptions: VPrivacyOptions = 130 | typeof binding.value === "boolean" 131 | ? { ...options, isPrivate: binding.value } 132 | : { ...options, ...binding.value }; 133 | 134 | updateStyle(el, finalOptions); 135 | manageTextEncrypt(el, finalOptions); 136 | }, 137 | }; 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![build](https://img.shields.io/npm/v/v-privacy)](https://www.npmjs.com/package/v-privacy) 2 | 3 |
4 | 5 |
6 | 7 | # v-privacy 8 | V-Privacy is a Vue 3 plugin that allows you to manage the privacy of an HTML element by blurring its content and encrypt node text data in DOM. This can be useful in scenarios where you want to hide sensitive information or content that is not relevant for the user. 9 | 10 | ## Installation 11 | 12 | To install V-Privacy, run the following command: 13 | 14 | ``` 15 | npm install --save v-privacy 16 | ``` 17 | 18 | ## Options 19 | 20 | | Option | Type | Default | Description | 21 | | ------- | -------- | -------- | -------- | 22 | | Blur | number | 5 | The amount of blur to apply to the element. | 23 | | blurColor | string | '' | The color of the background to be applied while blurring the element. | 24 | | disabledAction | boolean | true | If set to true, disables any action that can be taken on the element while it's blurred. | 25 | | disabledScreenReader | true | true | If set to true, hides the element from screen readers while it's blurred. | 26 | | isPrivate | boolean | false | If set to true, applies the privacy filter to the element. | 27 | | isSelectable | boolean | false | If set to true, allows the element to be selectable even if it's blurred. | 28 | | isTabbable | boolean | false | If set to true, the element becomes tabbable when it's blurred. | 29 | | transitionDelay | number | 0 | The delay before the transition starts. | 30 | | transitionDuration | number | 0.2 | The duration of the transition. | 31 | | transitionTimingFunction | string | 'linear' | The timing function of the transition. | 32 | | encryptText | boolean | false | If set to true, node text will be encrypt (Security ++) | 33 | | secretKey | string | '' | The secret key used to encrypt the node text directly in the DOM (If `encryptText` is set to true, this option is REQUIRED) | 34 | 35 | ## Usage 36 | 37 | ### Plugin Installation (Without encrypt option) 38 | 39 | To use V-Privacy in your Vue 3 project, you need to install it as a plugin. 40 | 41 | ```js 42 | import { createApp } from "vue"; 43 | import VPrivacy from "v-privacy"; 44 | 45 | const app = createApp(App); 46 | 47 | app.use(VPrivacy); 48 | ``` 49 | 50 | It is possible to provide an object to set default values (not all options are mandatory, and the ones not provided will be filled with the default values explained in Options): 51 | 52 | ```js 53 | app.use(VPrivacy, { 54 | blur: 10, 55 | transitionDuration: 1.2, 56 | blurColor: '#ff0000' 57 | } 58 | ); 59 | ``` 60 | 61 | 62 | ### Plugin Installation (With encrypt option) 63 | 64 | To use V-Privacy in your Vue 3 project with encrypt option, you need to install it as a plugin, and fill the secretKey option. 65 | 66 | 67 | WARNING: in order for this option to be really efficient, the secret key must be kept and used in a secure way. 68 | It is also necessary to keep in mind that despite the encryption of text nodes, the security of information coming from an API is not guaranteed. Indeed, the information can be found by the user via the return call of the API. 69 | 70 | 71 | ```js 72 | app.use(VPrivacy, { 73 | encryptText: true, 74 | secretKey: 'my-awesome-secret-key' 75 | } 76 | ); 77 | ``` 78 | 79 | ### Directive usage 80 | 81 | Using the v-privacy directive with a boolean value alone allows you to use the default options. The boolean impacts the isPrivate key and enables or disables the privacy effect. 82 | 83 | ```js 84 | 89 | 90 | 95 | ``` 96 | 97 | Using the v-privacy directive with an object allows you to change the options used (not all options are mandatory, and the ones not provided will be filled with the default values explained in Options), however, the use of isPrivate within the object is mandatory to work: 98 | 99 | ```js 100 | 105 | 106 | 111 | ``` 112 | 113 | ## Example 114 | 115 | Without Encrypt 116 | 117 | ![Demo GIF of Privacy](https://github.com/fchancel/v-privacy/blob/main/example/demo-privacy.gif) 118 | 119 | With Encrypt (The blur is voluntarily decreased on the demo in order to see the encryption) 120 | 121 | ![Demo GIF of Privacy](https://github.com/fchancel/v-privacy/blob/main/example/demo-privacy-encrypt.gif) 122 | 123 | ## Contributing 124 | 125 | Contributions, issues and feature requests are welcome! Feel free to check the [issues page](https://github.com/fchancel/v-privacy/issues) or create a pull request. 126 | 127 | ## License 128 | 129 | V-Privacy is licensed under the MIT License. See the [LICENSE](https://github.com/fchancel/v-privacy/blob/main/LICENSE) file for more information. 130 | --------------------------------------------------------------------------------