├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── forge.config.js ├── package-lock.json ├── package.json ├── src ├── controllers │ ├── decrypt_controller.js │ ├── encrypt_controller.js │ └── keys_controller.js ├── fonts │ ├── PressStart2P-Regular.ttf │ ├── RobotoMono-Bold.ttf │ ├── RobotoMono-Light.ttf │ └── RobotoMono-Regular.ttf ├── img │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── index.html ├── js │ ├── crypto.js │ ├── custom.js │ └── index.js ├── main.js ├── menu │ └── main-menu.js ├── renderer.js └── scss │ ├── _custom_bootstrap.scss │ ├── _style.scss │ └── app.scss ├── webpack.main.config.js ├── webpack.renderer.config.js ├── webpack.rules.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # Webpack 86 | .webpack/ 87 | 88 | # Electron-Forge 89 | out/ 90 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to make participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, sex characteristics, gender identity and expression, 10 | level of experience, education, socio-economic status, nationality, personal 11 | appearance, race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies within all project spaces, and it also applies when 50 | an individual is representing the project or its community in public spaces. 51 | Examples of representing a project or community include using an official 52 | project e-mail address, posting via an official social media account, or acting 53 | as an appointed representative at an online or offline event. Representation of 54 | a project may be further defined and clarified by project maintainers. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported by contacting the project team at hello@aliceandbob.io. All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | 76 | For answers to common questions about this code of conduct, see 77 | https://www.contributor-covenant.org/faq 78 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 aliceandbob.io 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aliceandbob.io - Desktop app 🖥️🔐 2 | 3 | A free, light and easy to use desktop app to generate PGP key pairs, encrypt and decrypt messages. The desktop app works fully offline to ensure maximum security to the user. 4 | 5 | ## Download it ⬇️ 6 | 7 | - From the repo's Github page: [releases](https://github.com/aliceandbob-io/aliceandbob-desktop/releases) 8 | - From [aliceandbob.io](https://aliceandbob.io/) 9 | - From [electronjs.org page](https://www.electronjs.org/apps/aliceandbob-io) 10 | 11 | ## Installation 12 | 13 | ⚠️ If you install the app on windows, you might get a warning from your anti-virus. It is because Windows builds are not signed. No worries, just add an exception for the app in your anti-virus software. 14 | 15 | ## Features ✨ 16 | 17 | - 🗝️ Generate PGP key pairs 18 | - 🔒 Encrypt messages with the public PGP key of the receiver 19 | - 🔓 Decrypt messages with your private PGP key 20 | 21 | ## Tech 🔧 22 | 23 | - [Electron.js](https://www.electronjs.org/): Electron.js helps building cross-platform desktop apps with JavaScript, HTML, and CSS 24 | - [Electron-forge](https://www.electronforge.io/): Electron Forge is a complete tool for creating, publishing, and installing modern Electron applications 25 | - HTML/CSS/Javascript 26 | - [Stimulus JS (v2.0.0)](https://stimulus.hotwire.dev/): A modest JavaScript framework 27 | - [Webpack](https://webpack.js.org/): The whole app is bundle with webpack through the electron-forge webpack plugin 28 | 29 | ## Library 📚 30 | 31 | OpenPGP.js (v5.3.0), a JavaScript implementation of the OpenPGP protocol. Find out more on [openpgpjs.org](https://openpgpjs.org/). 32 | 33 | ## Contributing 🍰 34 | 35 | Please contribute using [GitHub Flow](https://guides.github.com/introduction/flow). Refer first to the open issues then create a branch, add commits, and open a pull request! 36 | 37 | You can also read the [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md). 38 | 39 | When forking and cloning the repo, don't forget to do the following: 40 | 1. Make sure to have `node`, `npm` and `yarn` installed on your machine 41 | 2. run `yarn install` from the cloned repo. 42 | 3. Then, `yarn start` to launch aliceandbob.io Desktop App in dev mode. 43 | 44 | ⚠️ If you get any errors and warnings, install the necessary apps, packages or updates your platform may require. 45 | 46 | Find more info regarding the process and the configuration on the [electronforge.io](https://www.electronforge.io/) page. 47 | 48 | ## Building the desktop app locally 🏗️ 49 | 50 | If you want to package and build the app on your machine, run `yarn make --platform= --arch=`. 51 | As for the platform, you can choose either: 52 | - `win32`, 53 | - `linux`, 54 | - `darwin`, or 55 | - `mas`. 56 | 57 | As for the arch, you can choose either: 58 | - `x64`, 59 | - `ia32`, 60 | - `armv7l`, 61 | - `arm64`, or 62 | - `mips64el`. 63 | 64 | See all available options on the [electronPackager page](https://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html). 65 | 66 | Note that some specific builds cannot be done from some platforms. Learn more [here](https://www.electronforge.io/config/makers). 67 | 68 | ## Want to support the app? ❤️ 69 | 70 | Buy Me A Coffee 71 | 72 | ## License 📄 73 | 74 | Licensed under the [MIT License](LICENSE.md). 75 | -------------------------------------------------------------------------------- /forge.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | packagerConfig: { 5 | icon: path.resolve(__dirname, 'src/img/icon'), 6 | executableName: "aliceandbob.io" 7 | }, 8 | makers: [ 9 | { 10 | name: "@electron-forge/maker-squirrel", 11 | config: { 12 | iconUrl: "https://raw.githubusercontent.com/aliceandbob-io/files/main/icon.ico", 13 | setupIcon: path.resolve(__dirname, 'src/img/icon.ico') 14 | } 15 | }, 16 | { 17 | name: "@electron-forge/maker-zip", 18 | platforms: [ 19 | "darwin" 20 | ] 21 | }, 22 | { 23 | name: "@electron-forge/maker-deb", 24 | config: {} 25 | }, 26 | { 27 | name: "@electron-forge/maker-rpm", 28 | config: {} 29 | } 30 | ], 31 | plugins: [ 32 | [ 33 | "@electron-forge/plugin-webpack", 34 | { 35 | devContentSecurityPolicy: `default-src 'self' 'unsafe-inline' data:; script-src 'self' 'unsafe-eval' 'unsafe-inline' data:`, 36 | renderer: { 37 | config: "./webpack.renderer.config.js", 38 | entryPoints: [ 39 | { 40 | html: "./src/index.html", 41 | js: "./src/renderer.js", 42 | name: "main_window" 43 | } 44 | ] 45 | }, 46 | mainConfig: "./webpack.main.config.js" 47 | } 48 | ] 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aliceandbob.io", 3 | "productName": "aliceandbob.io", 4 | "version": "1.0.0", 5 | "description": "A free, light and easy to use PGP tool based on OpenPGP.js.", 6 | "main": "./.webpack/main", 7 | "scripts": { 8 | "start": "electron-forge start", 9 | "package": "electron-forge package", 10 | "make": "electron-forge make", 11 | "publish": "electron-forge publish", 12 | "lint": "echo \"No linting configured\"" 13 | }, 14 | "keywords": [], 15 | "author": { 16 | "name": "theolazian" 17 | }, 18 | "license": "MIT", 19 | "config": { 20 | "forge": "./forge.config.js" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.12.10", 24 | "@babel/plugin-proposal-class-properties": "^7.12.1", 25 | "@babel/plugin-transform-runtime": "^7.12.10", 26 | "@babel/preset-env": "^7.12.11", 27 | "@electron-forge/cli": "^6.0.0-beta.64", 28 | "@electron-forge/maker-deb": "^6.0.0-beta.64", 29 | "@electron-forge/maker-rpm": "^6.0.0-beta.63", 30 | "@electron-forge/maker-squirrel": "^6.0.0-beta.64", 31 | "@electron-forge/maker-zip": "^6.0.0-beta.63", 32 | "@electron-forge/plugin-webpack": "^6.0.0-beta.58", 33 | "@popperjs/core": "^2.6.0", 34 | "@vercel/webpack-asset-relocator-loader": "^1.7.0", 35 | "autoprefixer": "^10.2.1", 36 | "babel-loader": "^8.2.2", 37 | "css-loader": "^4.2.1", 38 | "electron": "^19.0.0", 39 | "file-loader": "^6.2.0", 40 | "jquery": "^3.6.0", 41 | "node-loader": "^1.0.1", 42 | "node-sass": "^5.0.0", 43 | "openpgp": "^5.3.0", 44 | "popper.js": "^1.16.1", 45 | "postcss": "^8.2.4", 46 | "postcss-loader": "^4.1.0", 47 | "sass": "^1.32.4", 48 | "sass-loader": "^10.1.1", 49 | "stimulus": "^2.0.0", 50 | "style-loader": "^1.2.1", 51 | "url-loader": "^4.1.1", 52 | "webpack": "^5.73.0" 53 | }, 54 | "dependencies": { 55 | "@electron/get": "^1.14.1", 56 | "@fortawesome/fontawesome-free": "^5.15.2", 57 | "bootstrap": "^4.6.0", 58 | "electron-squirrel-startup": "^1.0.0", 59 | "expose-loader": "^3.1.0", 60 | "material-icons": "^1.11.2" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/controllers/decrypt_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus"; 2 | import { decryptText } from '../js/crypto.js'; 3 | import { copy, showPass } from "../js/index.js"; 4 | 5 | export default class extends Controller { 6 | static targets = ["input", "output", "key", "passphrase", "initialState", "decryptButton", "error"]; 7 | 8 | async decrypt() { 9 | // Initial display 10 | this.initialStateTarget.classList.add("d-none"); 11 | this.inputTarget.classList.remove("border-danger"); 12 | this.keyTarget.classList.remove("border-danger"); 13 | this.passphraseTarget.classList.remove("border-danger"); 14 | this.errorTarget.classList.add("d-none"); 15 | 16 | // Get message and key 17 | const message = this.inputTarget.innerText; 18 | const key = this.keyTarget.innerText; 19 | const passphrase = this.passphraseTarget.value; 20 | 21 | // Validation form 22 | if (this.inputTarget.textContent == "") { 23 | this.inputTarget.classList.add("border-danger"); 24 | } 25 | if (this.keyTarget.textContent == "") { 26 | this.keyTarget.classList.add("border-danger"); 27 | } 28 | if (this.passphraseTarget.value == "") { 29 | this.passphraseTarget.classList.add("border-danger"); 30 | } 31 | if (this.keyTarget.textContent == "" || this.inputTarget.textContent == "" || this.passphraseTarget.value == "") { 32 | return 33 | } 34 | 35 | // Button UX 36 | this.decryptButtonTarget.disabled = true; 37 | this.decryptButtonTarget.getElementsByClassName("material-icons")[0].classList.add("d-none"); 38 | this.decryptButtonTarget.getElementsByClassName("spinner-border")[0].classList.remove("d-none"); 39 | 40 | const decrypted = await decryptText(message, key, passphrase).catch((err) => { console.error(err); }); 41 | 42 | if (decrypted) { 43 | this.outputTarget.innerText = decrypted; 44 | this.initialStateTarget.classList.remove("d-none"); 45 | this.errorTarget.classList.add("d-none"); 46 | $([document.documentElement, document.body]).animate({ 47 | scrollTop: $(this.initialStateTarget).offset().top 48 | }, 1000); 49 | } else { 50 | this.errorTarget.classList.remove("d-none"); 51 | $([document.documentElement, document.body]).animate({ 52 | scrollTop: 0 53 | }, 1000); 54 | } 55 | 56 | // Go back to initial UX button 57 | this.decryptButtonTarget.disabled = false; 58 | this.decryptButtonTarget.getElementsByClassName("material-icons")[0].classList.remove("d-none"); 59 | this.decryptButtonTarget.getElementsByClassName("spinner-border")[0].classList.add("d-none"); 60 | } 61 | 62 | showPassphrase(e) { 63 | let el = this.passphraseTarget; 64 | showPass(el); 65 | } 66 | 67 | copyToClipboard(e) { 68 | let el = e.target; 69 | let text; 70 | 71 | text = this.outputTarget.innerText; 72 | copy(text, el); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/controllers/encrypt_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus"; 2 | import { encryptText } from '../js/crypto.js'; 3 | import { copy } from "../js/index.js"; 4 | 5 | export default class extends Controller { 6 | static targets = ["input", "output", "key", "initialState", "encryptButton", "error"]; 7 | 8 | async encrypt() { 9 | // Initial display 10 | this.initialStateTarget.classList.add("d-none"); 11 | this.inputTarget.classList.remove("border-danger"); 12 | this.keyTarget.classList.remove("border-danger"); 13 | this.errorTarget.classList.add("d-none"); 14 | 15 | // Get message and key 16 | const message = this.inputTarget.innerText; 17 | const key = this.keyTarget.innerText; 18 | 19 | // Validation form 20 | if (this.inputTarget.textContent == "") { 21 | this.inputTarget.classList.add("border-danger"); 22 | } 23 | if (this.keyTarget.textContent == "") { 24 | this.keyTarget.classList.add("border-danger"); 25 | } 26 | if (this.keyTarget.textContent == "" || this.inputTarget.textContent == "") { 27 | return 28 | } 29 | 30 | // Button UX 31 | this.encryptButtonTarget.disabled = true; 32 | this.encryptButtonTarget.getElementsByClassName("material-icons")[0].classList.add("d-none"); 33 | this.encryptButtonTarget.getElementsByClassName("spinner-border")[0].classList.remove("d-none"); 34 | 35 | const encrypted = await encryptText(message, key).catch((err) => { console.error(err); }); 36 | 37 | if (encrypted) { 38 | this.outputTarget.innerText = encrypted; 39 | this.initialStateTarget.classList.remove("d-none"); 40 | this.errorTarget.classList.add("d-none"); 41 | $([document.documentElement, document.body]).animate({ 42 | scrollTop: $(this.initialStateTarget).offset().top 43 | }, 1000); 44 | } else { 45 | this.errorTarget.classList.remove("d-none"); 46 | $([document.documentElement, document.body]).animate({ 47 | scrollTop: 0 48 | }, 1000); 49 | } 50 | 51 | // Go back to initial UX button 52 | this.encryptButtonTarget.disabled = false; 53 | this.encryptButtonTarget.getElementsByClassName("material-icons")[0].classList.remove("d-none"); 54 | this.encryptButtonTarget.getElementsByClassName("spinner-border")[0].classList.add("d-none"); 55 | } 56 | 57 | copyToClipboard(e) { 58 | let el = e.target; 59 | let text; 60 | 61 | text = this.outputTarget.innerText; 62 | copy(text, el); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/controllers/keys_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus"; 2 | import { generateMyKeys } from '../js/crypto.js'; 3 | import { download, copy, showPass } from "../js/index.js"; 4 | 5 | export default class extends Controller { 6 | static targets = ["emailParams", "passphraseParams", "curveParams", "privateKey", "publicKey", "initialState", "generateButton", "error"]; 7 | 8 | async generate(e) { 9 | // Initial display 10 | e.preventDefault(); 11 | this.initialStateTarget.classList.add("d-none"); 12 | this.emailParamsTarget.classList.remove("border-danger"); 13 | this.passphraseParamsTarget.classList.remove("border-danger"); 14 | this.curveParamsTarget.classList.remove("border-danger"); 15 | this.errorTarget.classList.add("d-none"); 16 | 17 | // Validation form 18 | if (this.emailParamsTarget.value == "") { 19 | this.emailParamsTarget.classList.add("border-danger"); 20 | } 21 | if (this.passphraseParamsTarget.value == "") { 22 | this.passphraseParamsTarget.classList.add("border-danger"); 23 | } 24 | if (this.curveParamsTarget.value == "") { 25 | this.curveParamsTarget.classList.add("border-danger"); 26 | } 27 | if (this.emailParamsTarget.value == "" || this.passphraseParamsTarget.value == "" || this.curveParamsTarget.value == "") { 28 | return 29 | } 30 | 31 | // UX button 32 | this.generateButtonTarget.disabled = true; 33 | this.generateButtonTarget.getElementsByClassName("material-icons")[0].classList.add("d-none"); 34 | this.generateButtonTarget.getElementsByClassName("spinner-border")[0].classList.remove("d-none"); 35 | 36 | // Params 37 | const emailParams = this.emailParamsTarget.value; 38 | const passphraseParams = this.passphraseParamsTarget.value; 39 | const curveParams = this.curveParamsTarget.value; 40 | 41 | const key = await generateMyKeys(emailParams, passphraseParams, curveParams).catch((err) => { console.error(err); }); 42 | 43 | if (key) { 44 | this.privateKeyTarget.innerText = key.privateKey; 45 | this.publicKeyTarget.innerText = key.publicKey; 46 | this.initialStateTarget.classList.remove("d-none"); 47 | this.errorTarget.classList.add("d-none"); 48 | $([document.documentElement, document.body]).animate({ 49 | scrollTop: $(this.initialStateTarget).offset().top 50 | }, 1000); 51 | } else { 52 | this.errorTarget.classList.remove("d-none"); 53 | $([document.documentElement, document.body]).animate({ 54 | scrollTop: 0 55 | }, 1000); 56 | } 57 | 58 | // Go back to initial UX button 59 | this.generateButtonTarget.disabled = false; 60 | this.generateButtonTarget.getElementsByClassName("material-icons")[0].classList.remove("d-none"); 61 | this.generateButtonTarget.getElementsByClassName("spinner-border")[0].classList.add("d-none"); 62 | } 63 | 64 | showPassphrase(e) { 65 | let el = this.passphraseParamsTarget; 66 | showPass(el); 67 | } 68 | 69 | copyToClipboard(e) { 70 | let el = e.target; 71 | let text; 72 | 73 | if (el.classList.contains("public-key")) { 74 | text = this.publicKeyTarget.innerText; 75 | } else if (el.classList.contains("private-key")) { 76 | text = this.privateKeyTarget.innerText; 77 | } else { 78 | this.errorTarget.classList.remove("d-none"); 79 | $([document.documentElement, document.body]).animate({ 80 | scrollTop: 0 81 | }, 1000); 82 | return 83 | } 84 | 85 | copy(text, el); 86 | } 87 | 88 | downloadKey(e) { 89 | e.preventDefault(); 90 | let type = e.currentTarget.dataset.type; 91 | let format = e.currentTarget.dataset.format; 92 | if (type == "public") { 93 | const text = this.publicKeyTarget.innerText; 94 | if (format == "asc") { 95 | download(text, "text/asc", "aliceandbob.io - Public Key.asc"); 96 | } else { 97 | download(text, "text/txt", "aliceandbob.io - Public Key.txt"); 98 | } 99 | } else if (type == "private") { 100 | const text = this.privateKeyTarget.innerText; 101 | if (format == "asc") { 102 | download(text, "text/asc", "aliceandbob.io - Private Key.asc"); 103 | } else { 104 | download(text, "text/txt", "aliceandbob.io - Private Key.txt"); 105 | } 106 | } else { 107 | download("Let's hope you didn't have any bad intention by doing so ;)", "txt", "Well tried"); 108 | this.errorTarget.classList.remove("d-none"); 109 | $([document.documentElement, document.body]).animate({ 110 | scrollTop: 0 111 | }, 1000); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/fonts/PressStart2P-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliceandbob-io/aliceandbob-desktop/3dd83db39e9d91b8426880f2019ee255b5b48f58/src/fonts/PressStart2P-Regular.ttf -------------------------------------------------------------------------------- /src/fonts/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliceandbob-io/aliceandbob-desktop/3dd83db39e9d91b8426880f2019ee255b5b48f58/src/fonts/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /src/fonts/RobotoMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliceandbob-io/aliceandbob-desktop/3dd83db39e9d91b8426880f2019ee255b5b48f58/src/fonts/RobotoMono-Light.ttf -------------------------------------------------------------------------------- /src/fonts/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliceandbob-io/aliceandbob-desktop/3dd83db39e9d91b8426880f2019ee255b5b48f58/src/fonts/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /src/img/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliceandbob-io/aliceandbob-desktop/3dd83db39e9d91b8426880f2019ee255b5b48f58/src/img/icon.icns -------------------------------------------------------------------------------- /src/img/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliceandbob-io/aliceandbob-desktop/3dd83db39e9d91b8426880f2019ee255b5b48f58/src/img/icon.ico -------------------------------------------------------------------------------- /src/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliceandbob-io/aliceandbob-desktop/3dd83db39e9d91b8426880f2019ee255b5b48f58/src/img/icon.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aliceandbob.io 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 26 |
27 |
28 | 46 |
47 |
48 |
49 |
50 | Generate a PGP key pair 51 |
52 | An error has occured. Please verify the submitted data or try again later. 53 |
54 |
55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 |
There is no way to recover your passphrase so be sure not to forget it.
63 |
64 | 65 | 72 |
73 |
74 |
75 | 76 |
Choose the type of encryption for your key pair.
77 |
Brainpool and secp256k1 curves are no longer supported. See more
78 | 93 |
94 |
95 |
96 | 101 |
102 |
103 |
104 |
105 | 106 | content_copy 107 | Copied! 108 |
109 | 110 | save_alt.txt 111 | 112 | 113 | save_alt.asc 114 | 115 |
116 |
117 | 118 | content_copy 119 | Copied! 120 |
121 | 122 | save_alt.txt 123 | 124 | 125 | save_alt.asc 126 | 127 |
128 |
129 |
130 |
131 |
132 |
133 | Encrypt a message 134 |
135 | An error has occured. Please verify the submitted data or try again later. 136 |
137 |
138 |
139 | 140 |
141 |
142 |
143 | 144 |
145 |
146 |
147 | 152 |
153 |
154 |
155 |
156 |
157 | 158 | content_copy 159 | Copied! 160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | Decrypt a message 168 |
169 | An error has occured. Please verify the submitted data or try again later. 170 |
171 |
172 |
173 | 174 |
175 |
176 |
177 | 178 |
179 |
180 |
181 |
182 | 183 |
184 | 185 | 192 |
193 |
194 |
195 |
196 | 201 |
202 |
203 |
204 |
205 |
206 | 207 | content_copy 208 | Copied! 209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 | 243 |
244 |
245 |
246 | 247 | 248 | -------------------------------------------------------------------------------- /src/js/crypto.js: -------------------------------------------------------------------------------- 1 | import { 2 | generateKey, 3 | readKeys, 4 | encrypt, 5 | readMessage, 6 | readPrivateKey, 7 | decryptKey, 8 | decrypt, 9 | createMessage 10 | } from 'openpgp'; 11 | 12 | export async function generateMyKeys(emailParams, passphraseParams, curveParams) { 13 | const name = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 14 | const email = emailParams; 15 | const passphrase = passphraseParams; 16 | const curve = curveParams; 17 | return await generateKey({ 18 | curve: curveParams, 19 | userIDs: [{ name: name, email: email }], 20 | passphrase: passphrase 21 | }); 22 | } 23 | 24 | export async function encryptText(text, armoredKeys) { 25 | const publicKeys = await readKeys({ armoredKeys }); 26 | 27 | const encrypted = await encrypt({ 28 | message: await createMessage({ text }), 29 | encryptionKeys: publicKeys 30 | }); 31 | 32 | return encrypted; 33 | } 34 | 35 | export async function decryptText(armoredMessage, armoredKey, passphrase) { 36 | const privateKey = await readPrivateKey({ armoredKey }); 37 | const decryptedPrivateKey = await decryptKey({ privateKey, passphrase }); 38 | 39 | const { data: decrypted } = await decrypt({ 40 | message: await readMessage({ armoredMessage }), 41 | decryptionKeys: decryptedPrivateKey 42 | }); 43 | 44 | return decrypted; 45 | } 46 | -------------------------------------------------------------------------------- /src/js/custom.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $(function () { 3 | $('[data-toggle="popover"]').popover() 4 | }) 5 | 6 | $(function () { 7 | $('[data-toggle="tooltip"]').tooltip() 8 | }) 9 | 10 | $('a[data-toggle="pill"]').on('shown.bs.tab', function (e) { 11 | window.scrollTo(0, 0); 12 | }) 13 | }); 14 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | export function download(text, fileType, fileName) { 2 | var blob = new Blob([text], { type: fileType }); 3 | 4 | var a = document.createElement('a'); 5 | a.download = fileName; 6 | a.href = URL.createObjectURL(blob); 7 | a.dataset.downloadurl = [fileType, a.download, a.href].join(':'); 8 | a.style.display = "none"; 9 | document.body.appendChild(a); 10 | a.click(); 11 | document.body.removeChild(a); 12 | setTimeout(function() { URL.revokeObjectURL(a.href); }, 1500); 13 | } 14 | 15 | export function showPass(el) { 16 | if (el.type === "password") { 17 | el.type = "text"; 18 | } else { 19 | el.type = "password"; 20 | } 21 | } 22 | 23 | export function copy(text, el) { 24 | const textArea = document.createElement('textarea'); 25 | document.body.appendChild(textArea); 26 | textArea.value = text; 27 | textArea.select(); 28 | document.execCommand('copy'); 29 | document.body.removeChild(textArea); 30 | 31 | $(el).closest('[class^="col"]').find('.badge').removeClass("d-none"); 32 | setTimeout(function() { 33 | $(el).closest('[class^="col"]').find('.badge').addClass("d-none"); 34 | },1000); 35 | } 36 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, shell, Menu } = require('electron'); 2 | const path = require('path'); 3 | 4 | // Handle creating/removing shortcuts on Windows when installing/uninstalling. 5 | if (require('electron-squirrel-startup')) { // eslint-disable-line global-require 6 | app.quit(); 7 | } 8 | 9 | const createWindow = () => { 10 | // Create the browser window. 11 | const mainWindow = new BrowserWindow({ 12 | show: false, 13 | width: 1000, 14 | height: 900, 15 | backgroundColor: "#000", 16 | webPreferences: { 17 | preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY 18 | } 19 | }); 20 | 21 | // and load the index.html of the app. 22 | mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); 23 | 24 | // Open links in default browser 25 | mainWindow.webContents.on('new-window', function(event, url){ 26 | event.preventDefault(); 27 | shell.openExternal(url); 28 | }); 29 | 30 | // Open the DevTools. 31 | // mainWindow.webContents.openDevTools(); 32 | 33 | // Call main-menu.js file 34 | require('./menu/main-menu') 35 | 36 | // Show mainWindow only when all html and js files are loaded 37 | mainWindow.webContents.on('did-finish-load', () => { 38 | mainWindow.show(); 39 | }); 40 | 41 | mainWindow.webContents.on('did-start-loading', () => { 42 | // Find a solution to handle raw index.html to be shown 43 | // Temporarly disabling menu reload to do so. 44 | }); 45 | }; 46 | 47 | // This method will be called when Electron has finished 48 | // initialization and is ready to create browser windows. 49 | // Some APIs can only be used after this event occurs. 50 | app.on('ready', createWindow); 51 | 52 | // Quit when all windows are closed, except on macOS. There, it's common 53 | // for applications and their menu bar to stay active until the user quits 54 | // explicitly with Cmd + Q. 55 | app.on('window-all-closed', () => { 56 | if (process.platform !== 'darwin') { 57 | app.quit(); 58 | } 59 | }); 60 | 61 | app.on('activate', () => { 62 | // On OS X it's common to re-create a window in the app when the 63 | // dock icon is clicked and there are no other windows open. 64 | if (BrowserWindow.getAllWindows().length === 0) { 65 | createWindow(); 66 | } 67 | }); 68 | 69 | // In this file you can include the rest of your app's specific main process 70 | // code. You can also put them in separate files and import them here. 71 | -------------------------------------------------------------------------------- /src/menu/main-menu.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, shell, Menu } = require('electron'); 2 | const path = require('path'); 3 | 4 | const isMac = process.platform === 'darwin' 5 | 6 | const template = [ 7 | // { role: 'appMenu' } 8 | ...(isMac ? [{ 9 | label: app.name, 10 | submenu: [ 11 | { role: 'about' }, 12 | { type: 'separator' }, 13 | { role: 'services' }, 14 | { type: 'separator' }, 15 | { role: 'hide' }, 16 | { role: 'hideothers' }, 17 | { role: 'unhide' }, 18 | { type: 'separator' }, 19 | { role: 'quit' } 20 | ] 21 | }] : []), 22 | // { role: 'fileMenu' } 23 | { 24 | label: 'File', 25 | submenu: [ 26 | isMac ? { role: 'close' } : { role: 'quit' } 27 | ] 28 | }, 29 | // { role: 'editMenu' } 30 | { 31 | label: 'Edit', 32 | submenu: [ 33 | { role: 'undo' }, 34 | { role: 'redo' }, 35 | { type: 'separator' }, 36 | { role: 'cut' }, 37 | { role: 'copy' }, 38 | { role: 'paste' }, 39 | ...(isMac ? [ 40 | { role: 'pasteAndMatchStyle' }, 41 | { role: 'delete' }, 42 | { role: 'selectAll' }, 43 | { type: 'separator' }, 44 | { 45 | label: 'Speech', 46 | submenu: [ 47 | { role: 'startSpeaking' }, 48 | { role: 'stopSpeaking' } 49 | ] 50 | } 51 | ] : [ 52 | { role: 'delete' }, 53 | { type: 'separator' }, 54 | { role: 'selectAll' } 55 | ]) 56 | ] 57 | }, 58 | // { role: 'viewMenu' } 59 | { 60 | label: 'View', 61 | submenu: [ 62 | { role: 'resetZoom' }, 63 | { role: 'zoomIn' }, 64 | { role: 'zoomOut' }, 65 | { type: 'separator' }, 66 | { 67 | label: 'Developer', 68 | submenu: [ 69 | { role: 'reload' }, 70 | { role: 'forceReload' }, 71 | { role: 'toggleDevTools' } 72 | ] 73 | }, 74 | { type: 'separator' }, 75 | { role: 'togglefullscreen' } 76 | ] 77 | }, 78 | // { role: 'windowMenu' } 79 | { 80 | label: 'Window', 81 | submenu: [ 82 | { role: 'minimize' }, 83 | { role: 'zoom' }, 84 | ...(isMac ? [ 85 | { type: 'separator' }, 86 | { role: 'front' }, 87 | { type: 'separator' }, 88 | { role: 'window' } 89 | ] : [ 90 | { role: 'close' } 91 | ]) 92 | ] 93 | }, 94 | { 95 | role: 'help', 96 | submenu: [ 97 | { 98 | label: 'Learn More', 99 | click: async () => { 100 | await shell.openExternal('https://electronjs.org') 101 | } 102 | }, 103 | { 104 | label: 'Visit aliceandbob.io', 105 | click: async () => { 106 | await shell.openExternal('https://aliceandbob.io') 107 | } 108 | } 109 | ] 110 | } 111 | ] 112 | 113 | const menu = Menu.buildFromTemplate(template) 114 | Menu.setApplicationMenu(menu) 115 | -------------------------------------------------------------------------------- /src/renderer.js: -------------------------------------------------------------------------------- 1 | console.log('👋 This message is being logged by "renderer.js", included via webpack'); 2 | 3 | import $ from "jquery"; 4 | 5 | // Import Bootstrap and scss 6 | import 'bootstrap'; 7 | import './scss/app.scss'; 8 | 9 | // Import js files 10 | import './js/custom.js'; 11 | 12 | // FONTAWESOME 13 | import "@fortawesome/fontawesome-free/js/all"; 14 | FontAwesome.config.mutateApproach = 'sync' 15 | 16 | // Initialize Stimulus and controllers 17 | import { Application } from "stimulus" 18 | import { definitionsFromContext } from "stimulus/webpack-helpers" 19 | 20 | const application = Application.start() 21 | const context = require.context("./controllers", true, /\.js$/) 22 | application.load(definitionsFromContext(context)) 23 | -------------------------------------------------------------------------------- /src/scss/_custom_bootstrap.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Roboto mono"; 3 | src: url("../fonts/RobotoMono-Light.ttf"); 4 | font-weight: 300; 5 | font-size: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: "Roboto mono"; 10 | src: url("../fonts/RobotoMono-Regular.ttf"); 11 | font-weight: 400; 12 | font-size: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: "Roboto mono"; 17 | src: url("../fonts/RobotoMono-Bold.ttf"); 18 | font-weight: 700; 19 | font-size: normal; 20 | } 21 | 22 | @font-face { 23 | font-family: "PressStart"; 24 | src: url("../fonts/PressStart2P-Regular.ttf"); 25 | } 26 | 27 | $theme-colors: ( 28 | "primary": #e5e619, 29 | "secondary": #FF468B, 30 | "info": #00E3FF 31 | ); 32 | -------------------------------------------------------------------------------- /src/scss/_style.scss: -------------------------------------------------------------------------------- 1 | $my-primary: #e5e619; 2 | 3 | body { 4 | font-family: 'Roboto mono', sans-serif; 5 | p { 6 | font-weight: 300; 7 | } 8 | } 9 | 10 | .full-background { 11 | background: linear-gradient( 12 | 30deg, 13 | /* 1st line */ 14 | rgba(255,255,255,0) 15%, 15 | rgba(255,70,139,0.7) 15%, 16 | rgba(255,70,139,0.7) 30%, 17 | rgba(255,255,255,0) 30%, 18 | /* 2nd line */ 19 | rgba(255,255,255,0) 50%, 20 | rgba(255,70,139,1) 50%, 21 | rgba(255,70,139,1) 60%, 22 | rgba(255,255,255,0) 60%, 23 | /* 3rd line */ 24 | rgba(255,255,255,0) 80%, 25 | rgba(255,70,139,0.7) 80%, 26 | rgba(255,70,139,0.7) 85%, 27 | rgba(255,255,255,0) 85%); 28 | } 29 | 30 | .highlight { 31 | background-repeat: no-repeat; 32 | background-size: 100% 0.4em; 33 | background-position: 0 100%; 34 | background-image: linear-gradient(120deg, rgba(0, 227, 255, 0.7) 0%, rgba(0, 227, 255, 0.7) 100%); 35 | } 36 | 37 | .nav-pills .nav-link.active, .nav-pills .show>.nav-link { 38 | color: black !important; 39 | } 40 | 41 | .navbar-brand { 42 | margin-right: 0px; 43 | font-family: 'PressStart', cursive; 44 | font-size: 1.5em; 45 | &:hover { 46 | color: $my-primary !important; 47 | } 48 | .shift { 49 | font-size: 0.7em; 50 | &:nth-last-of-type(-n+2) { 51 | bottom: -3px; 52 | position: relative; 53 | left: -8px; 54 | } 55 | &:last-of-type { 56 | left: -16px; 57 | } 58 | } 59 | } 60 | 61 | .navbar { 62 | >div { 63 | display: inline-flex; 64 | } 65 | .nav-link { 66 | color: gray; 67 | &:hover { 68 | color: $my-primary; 69 | } 70 | } 71 | } 72 | 73 | .form-control, .form-control:focus, .form-control:hover { 74 | background-color: rgba(0,0,0,0.2); 75 | color: white; 76 | border: 1px solid transparent; 77 | text-overflow: ellipsis; 78 | box-shadow: none; 79 | &:focus, &:hover { 80 | border: 1px solid white; 81 | } 82 | } 83 | 84 | .input-group-text { 85 | background-color: rgba(0,0,0,0.2); 86 | color: grey; 87 | border: none; 88 | box-shadow: none; 89 | } 90 | 91 | .input-group-append > a { 92 | text-decoration: none; 93 | cursor: pointer; 94 | } 95 | 96 | #content { 97 | display: flex; 98 | flex-direction: row; 99 | background-color: #202124; 100 | color: #fff; 101 | #nav { 102 | height: 100vh; 103 | background-color: black; 104 | position: sticky; 105 | top: 0; 106 | >div { 107 | display: flex; 108 | flex-direction: column; 109 | align-items: center; 110 | >a { 111 | margin: 0.5rem 0.5rem 2rem 0.5rem; 112 | transition: all .2s ease-in-out; 113 | >svg, span { 114 | margin: 0.75rem; 115 | } 116 | } 117 | } 118 | } 119 | #main { 120 | flex: 1; 121 | #page { 122 | min-height: calc(100vh - 62px - 16px - 50px - 100px - 100px); //100vh - header - header margin - footer margin - footer height - footer padding 123 | } 124 | footer { 125 | color: grey; 126 | margin-top: 50px; 127 | //min-height: 150px; 128 | padding: 50px 20px; 129 | width: 100%; 130 | a { 131 | color: gray; 132 | &:hover { 133 | color: $my-primary; 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | @include media-breakpoint-down(sm) { 141 | #content { 142 | display: flex; 143 | flex-direction: column-reverse; 144 | min-height: 100vh; 145 | #main { 146 | padding-bottom: 100px; 147 | } 148 | #nav { 149 | z-index: 1; 150 | height: auto; 151 | width: 100%; 152 | position: fixed; 153 | top: auto; 154 | bottom: 0; 155 | >div { 156 | display: flex; 157 | flex-direction: row; 158 | align-items: center; 159 | justify-content: space-around; 160 | >a { 161 | margin: 0.5rem 0.5rem 0.5rem 0.5rem; 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | @include media-breakpoint-down(sm) { 169 | .navbar { 170 | .nav-link { 171 | padding: 0.8rem; 172 | } 173 | .nav-link:last-child { 174 | padding-right: 0px; 175 | } 176 | } 177 | .navbar-brand { 178 | font-size: 1.2em; 179 | } 180 | .shift { 181 | &:nth-last-of-type(-n+2) { 182 | bottom: -3px; 183 | position: relative; 184 | left: -5px !important; 185 | } 186 | &:last-of-type { 187 | left: -10px !important; 188 | } 189 | } 190 | } 191 | 192 | fieldset { 193 | background-color: rgba(0,0,0,0.2); 194 | border-radius: 8px; 195 | padding: 25px; 196 | legend { 197 | font-weight: 700; 198 | } 199 | label { 200 | font-weight: 700; 201 | color: grey; 202 | } 203 | } 204 | 205 | .custom-textarea { 206 | background-color: rgba(0,0,0,0.2); 207 | min-height: 35vh; 208 | max-height: 35vh; 209 | margin-bottom: 1rem; 210 | overflow: auto; 211 | color: white; 212 | padding: 15px 25px; 213 | font-weight: 300; 214 | border: 1px solid rgba(0,0,0,0.2); 215 | border-radius: 8px; 216 | word-break: break-word; 217 | &:focus, &:hover { 218 | outline: none; 219 | border-radius: 8px; 220 | border: 1px solid #ced4da; 221 | } 222 | &::-webkit-scrollbar-thumb { 223 | background-color: white; 224 | border: 4px solid transparent; 225 | border-radius: 8px; 226 | background-clip: padding-box; 227 | } 228 | &::-webkit-scrollbar { 229 | width: 15px; 230 | } 231 | &::-webkit-scrollbar-track { 232 | background: none; 233 | } 234 | scrollbar-color: white black; 235 | scrollbar-width: thin; 236 | } 237 | 238 | select option { 239 | background: black; 240 | color: #fff; 241 | } 242 | 243 | .material-icons, .spinner-border { 244 | font-size: 24px; 245 | vertical-align: middle; 246 | position: relative; 247 | } 248 | 249 | // Fix electron app 250 | .download > .material-icons { 251 | width: 24px; 252 | } 253 | 254 | .icon-fix { 255 | top: -1px; 256 | } 257 | 258 | .copy, .copy-badge { 259 | position: absolute; 260 | right: 30px; 261 | top: 40px; 262 | cursor: pointer; 263 | } 264 | 265 | .copy-badge { 266 | line-height: 18px; 267 | } 268 | 269 | .download { 270 | margin-bottom: 3rem; 271 | display: inline-block; 272 | } 273 | 274 | .error { 275 | font-weight: 300; 276 | } 277 | -------------------------------------------------------------------------------- /src/scss/app.scss: -------------------------------------------------------------------------------- 1 | @import "custom_bootstrap"; 2 | @import "~bootstrap/scss/bootstrap"; 3 | @import "~material-icons/iconfont/material-icons.css"; 4 | @import '@fortawesome/fontawesome-free/scss/fontawesome'; 5 | @import "style"; 6 | -------------------------------------------------------------------------------- /webpack.main.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | /** 5 | * This is the main entry point for your application, it's the first file 6 | * that runs in the main process. 7 | */ 8 | entry: './src/main.js', 9 | // Put your normal webpack config below here 10 | module: { 11 | rules: require('./webpack.rules'), 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const rules = require('./webpack.rules'); 3 | 4 | rules.push( 5 | { 6 | test: /\.(scss)$/, 7 | use: [ 8 | { 9 | loader: 'style-loader', 10 | }, 11 | { 12 | loader: 'css-loader', 13 | }, 14 | { 15 | loader: 'postcss-loader', 16 | options: { 17 | postcssOptions: { 18 | plugins: function () { 19 | return [ 20 | require('autoprefixer') 21 | ]; 22 | } 23 | } 24 | } 25 | }, 26 | { 27 | loader: 'sass-loader' 28 | } 29 | ] 30 | }, 31 | { 32 | test: /\.(png|jpe?g|gif|ico|svg)$/, 33 | use: [ 34 | { 35 | loader: "file-loader", 36 | options: { 37 | publicPath: "./../", 38 | name: "./img/[hash].[ext]" 39 | //outputPath: '' 40 | } 41 | } 42 | ] 43 | }, 44 | { 45 | test: /\.(woff|woff2|ttf|otf|eot)$/, 46 | use: [ 47 | { 48 | loader: "file-loader", 49 | options: { 50 | publicPath: "./../", 51 | name: "./fonts/[hash].[ext]" 52 | //outputPath: '' 53 | } 54 | } 55 | ] 56 | } 57 | ); 58 | 59 | module.exports = { 60 | // Put your normal webpack config below here 61 | module: { 62 | rules, 63 | }, 64 | plugins: [ 65 | new webpack.ProvidePlugin({ 66 | $: 'jquery', 67 | jQuery: 'jquery' 68 | }) 69 | ] 70 | }; 71 | -------------------------------------------------------------------------------- /webpack.rules.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | // Add support for native node modules 3 | { 4 | test: /native_modules\/.+\.node$/, 5 | use: 'node-loader', 6 | }, 7 | { 8 | test: /\.(m?js|node)$/, 9 | parser: { amd: false }, 10 | use: { 11 | loader: '@vercel/webpack-asset-relocator-loader', 12 | options: { 13 | outputAssetBase: 'native_modules', 14 | }, 15 | }, 16 | }, 17 | { 18 | test: /\.js$/, 19 | exclude: /(node_modules)/, 20 | use: { 21 | loader: 'babel-loader', 22 | options: { 23 | presets: ['@babel/preset-env'], 24 | plugins: [ 25 | "@babel/plugin-proposal-class-properties", 26 | "@babel/plugin-transform-runtime" 27 | ] 28 | } 29 | } 30 | }, 31 | { 32 | test: require.resolve("jquery"), 33 | loader: "expose-loader", 34 | options: { 35 | exposes: ["$", "jQuery"] 36 | }, 37 | }, 38 | ]; 39 | --------------------------------------------------------------------------------