├── app ├── localization │ ├── locales │ │ ├── ar │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── de │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── hi │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── hr │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── hu │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── id │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── it │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── ja │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── kn │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── ko │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── lt │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── lv │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── ml │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── mr │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── ms │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── nl │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── no │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── pl │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── pt │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── ro │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── ru │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── sk │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── sr │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── sv │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── sw │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── ta │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── te │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── th │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── tr │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── uk │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── vi │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── zh_CN │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── af │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── am │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── bg │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── ca │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── cs │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── da │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── el │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── en │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── es │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── et │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── fa │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── fi │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── fil │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── fr │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ ├── gu │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ │ └── he │ │ │ ├── translation.missing.json │ │ │ └── translation.json │ ├── i18n.mainconfig.js │ ├── i18n.config.js │ ├── whitelist.js │ └── translateMissing.js ├── src │ ├── pages │ │ ├── localization │ │ │ ├── localization.css │ │ │ └── localization.jsx │ │ ├── undoredo │ │ │ ├── undoredo.css │ │ │ └── undoredo.jsx │ │ ├── image │ │ │ └── image.jsx │ │ ├── about │ │ │ └── about.jsx │ │ ├── welcome │ │ │ └── welcome.jsx │ │ ├── contextmenu │ │ │ └── contextmenu.jsx │ │ └── motd │ │ │ └── motd.jsx │ ├── core │ │ ├── root.css │ │ ├── root.jsx │ │ ├── routes.jsx │ │ └── nav.jsx │ ├── index.html │ ├── constants │ │ └── routes.json │ ├── redux │ │ ├── components │ │ │ ├── counter │ │ │ │ └── counterSlice.js │ │ │ ├── home │ │ │ │ └── homeSlice.js │ │ │ └── complex │ │ │ │ └── complexSlice.js │ │ └── store │ │ │ └── store.js │ ├── index.tsx │ └── components │ │ └── subitem │ │ └── subitem.jsx └── electron │ ├── preload.js │ ├── protocol.js │ ├── menu.js │ └── main.js ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── codeql-analysis.yml ├── docs ├── imgs │ ├── ipc.png │ ├── intro.gif │ ├── pre-v5.png │ ├── post-v5.png │ └── usethistemplate.png ├── yourapps.md ├── app.md ├── faq.md ├── scripts.md ├── src.md ├── architecture.md ├── secureapps.md ├── sandbox.md └── newtoelectron.md ├── resources ├── icon.ico ├── icon.png ├── icon.icns └── images │ └── testimage.png ├── CODE_OF_CONDUCT.md ├── .prettierrc ├── CONTRIBUTING.md ├── tsconfig.json ├── .babelrc ├── dev-scripts ├── prepareDevServer.js └── launchDevServer.js ├── LICENSE ├── webpack.development.js ├── webpack.production.js ├── test └── spec.js ├── .gitignore ├── webpack.config.js ├── package.json └── README.md /app/localization/locales/ar/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/de/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/hi/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/hr/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/hu/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/id/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/it/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/ja/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/kn/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/ko/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/lt/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/lv/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/ml/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/mr/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/ms/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/nl/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/no/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/pl/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/pt/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/ro/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/ru/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/sk/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/sr/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/sv/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/sw/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/ta/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/te/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/th/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/tr/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/uk/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/vi/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/zh_CN/translation.missing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/localization/locales/af/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/am/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/bg/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/ca/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/cs/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/da/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/el/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/en/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/es/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/et/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/fa/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/fi/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/fil/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/fr/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/gu/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /app/localization/locales/he/translation.missing.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | tab_width = 2 -------------------------------------------------------------------------------- /app/src/pages/localization/localization.css: -------------------------------------------------------------------------------- 1 | .italics { 2 | font-style: italic; 3 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: reZach 4 | -------------------------------------------------------------------------------- /docs/imgs/ipc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/docs/imgs/ipc.png -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/resources/icon.png -------------------------------------------------------------------------------- /docs/imgs/intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/docs/imgs/intro.gif -------------------------------------------------------------------------------- /docs/imgs/pre-v5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/docs/imgs/pre-v5.png -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/resources/icon.icns -------------------------------------------------------------------------------- /docs/imgs/post-v5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/docs/imgs/post-v5.png -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | Be civil, be nice, be respectful. Behave like a good human, and we'll all get along. -------------------------------------------------------------------------------- /docs/imgs/usethistemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/docs/imgs/usethistemplate.png -------------------------------------------------------------------------------- /resources/images/testimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reZach/secure-electron-template/HEAD/resources/images/testimage.png -------------------------------------------------------------------------------- /docs/yourapps.md: -------------------------------------------------------------------------------- 1 | # Your apps! 2 | Show us the apps you've built with our template! Create a pull request and modify this file to add your app to the list. 3 | 4 | - 5 | 6 | -------------------------------------------------------------------------------- /app/src/core/root.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | margin: 0px; 7 | height: -webkit-fill-available; 8 | } 9 | 10 | #target { 11 | height: -webkit-fill-available; 12 | } -------------------------------------------------------------------------------- /app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/constants/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "WELCOME": "/", 3 | "ABOUT": "/about", 4 | "MOTD": "/motd", 5 | "LOCALIZATION": "/localization", 6 | "UNDOREDO": "/undoredo", 7 | "CONTEXTMENU": "/contextmenu", 8 | "IMAGE": "/image" 9 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": false, 4 | "jsxSingleQuote": false, 5 | "quoteProps": "consistent", 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": true, 8 | "arrowParens": "always" 9 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please provide the following information: 11 | 12 | **Operating system**: 13 | **Version of the template**: 14 | 15 | Please have a MVP (minimal viable product/example) that can reproduce the bug consistently. If you do not have a MVP, the issue will not get as much attention to fixing. 16 | 17 | Thank you for taking the time to help make this project better. 18 | -------------------------------------------------------------------------------- /app/src/pages/undoredo/undoredo.css: -------------------------------------------------------------------------------- 1 | #undoredo { 2 | display: grid; 3 | height: 100%; 4 | justify-items: center; 5 | grid-template-columns: auto; 6 | grid-template-rows: 40px 2fr 4fr 2fr 1fr 40px; 7 | } 8 | 9 | .undoredo { 10 | font-size: 50px; 11 | text-align: center; 12 | grid-column: 1; 13 | grid-row: 2; 14 | } 15 | 16 | .left { 17 | justify-self: start; 18 | } 19 | 20 | .undo-container { 21 | max-height: 300px; 22 | overflow-y: scroll; 23 | background-color: #282c34; 24 | color: #6d9cbe; 25 | } -------------------------------------------------------------------------------- /app/src/redux/components/counter/counterSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const counterSlice = createSlice({ 4 | name: "counter", 5 | initialState: { 6 | value: 0 7 | }, 8 | reducers: { 9 | increment(state, _action) { 10 | state.value++; 11 | }, 12 | decrement(state, _action) { 13 | state.value--; 14 | } 15 | } 16 | }); 17 | 18 | // Export actions 19 | export const { increment, decrement } = counterSlice.actions; 20 | 21 | // Export reducer 22 | export default counterSlice.reducer; 23 | -------------------------------------------------------------------------------- /app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import i18n from "I18n/i18n.config"; 4 | import { I18nextProvider } from "react-i18next"; 5 | import Root from "Core/root"; 6 | import { store, history } from "Redux/store/store"; 7 | import "bulma/css/bulma.css"; 8 | 9 | const container = document.getElementById("target"); 10 | const root = createRoot(container); 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | ); -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | Please do the following steps before submitting a PR, doing this will help progress your fix into the template. 3 | 4 | 1. Have you ran `npm run test` to ensure the unit tests are working? 5 | 2. Have you ran `npm run dev` to see if the template works? Have you checked all of the pages to make sure they are still functional? 6 | 3. Have you ran `npm run prod` to see if the template works in production? Have you checked all of the pages to make sure they are still functional? 7 | 4. Have you ran `npm run dist-[windows|mac|linux|all]` to make sure the app is packaged correctly and works if you install it? -------------------------------------------------------------------------------- /app/src/redux/components/home/homeSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const homeSlice = createSlice({ 4 | name: "home", 5 | initialState: { 6 | message: 7 | typeof window.api.store.initial()["motd"] !== "undefined" 8 | ? window.api.store.initial()["motd"] 9 | : "Hello and welcome to the template!" 10 | }, 11 | reducers: { 12 | changeMessage(state, action) { 13 | state.message = action.payload; 14 | } 15 | } 16 | }); 17 | 18 | // Export actions 19 | export const { changeMessage } = homeSlice.actions; 20 | 21 | // Export reducer 22 | export default homeSlice.reducer; 23 | -------------------------------------------------------------------------------- /app/localization/locales/zh_CN/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "你好", 3 | "File": "文件", 4 | "Exit": "退出", 5 | "Edit": "编辑", 6 | "Undo": "撤消", 7 | "Redo": "重做", 8 | "Cut": "剪切", 9 | "Copy": "复制", 10 | "Paste": "粘贴", 11 | "Delete": "删除", 12 | "Select All": "全选", 13 | "View": "视图", 14 | "Reload": "重新加载", 15 | "Force Reload": "强制重新加载", 16 | "Toggle Developer Tools": "开发者工具", 17 | "Reset Zoom": "重置缩放", 18 | "Zoom In": "放大", 19 | "Zoom Out": "缩小", 20 | "Toggle Fullscreen": "切换全屏", 21 | "Language": "语言", 22 | "Window": "窗口", 23 | "Minimize": "最小化", 24 | "Zoom": "放大", 25 | "Close": "关闭", 26 | "Help": "帮助", 27 | "Learn More": "了解更多" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/ko/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "안녕하세요", 3 | "File": "파일", 4 | "Exit": "종료", 5 | "Edit": "편집", 6 | "Undo": "실행 취소", 7 | "Redo": "다시 실행", 8 | "Cut": "자르기", 9 | "Copy": "복사", 10 | "Paste": "붙여넣기", 11 | "Delete": "삭제", 12 | "Select All": "모두 선택", 13 | "View": "화면", 14 | "Reload": "새로 고침", 15 | "Force Reload": "강한 새로고침", 16 | "Toggle Developer Tools": "개발자 도구 열기", 17 | "Reset Zoom": "확대 초기화", 18 | "Zoom In": "확대", 19 | "Zoom Out": "축소", 20 | "Toggle Fullscreen": "전체 화면 전환", 21 | "Language": "언어 선택", 22 | "Window": "창", 23 | "Minimize": "최소화", 24 | "Zoom": "줌", 25 | "Close": "닫기", 26 | "Help": "도움", 27 | "Learn More": "더 알아보기" 28 | } 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /app/src/pages/image/image.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import img from "Images/testimage.png"; 4 | 5 | class Image extends React.Component { 6 | render() { 7 | return ( 8 |
9 |
10 |

Loading images

11 |
12 |
13 | This page is to demonstrate that we can load an image hosted from a 14 | directory in our project. 15 |
16 |
17 | 18 |
19 |
20 | ); 21 | } 22 | } 23 | 24 | export default Image; 25 | -------------------------------------------------------------------------------- /app/localization/locales/ja/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "こんにちは", 3 | "File": "ファイル", 4 | "Exit": "出口", 5 | "Edit": "編集", 6 | "Undo": "元に戻す", 7 | "Redo": "やり直し", 8 | "Cut": "切る", 9 | "Copy": "コピー", 10 | "Paste": "ペースト", 11 | "Delete": "削除", 12 | "Select All": "すべて選択", 13 | "View": "見る", 14 | "Reload": "リロード", 15 | "Force Reload": "強制リロード", 16 | "Toggle Developer Tools": "開発者ツールの切り替え", 17 | "Reset Zoom": "ズームをリセット", 18 | "Zoom In": "ズームイン", 19 | "Zoom Out": "ズームアウトする", 20 | "Toggle Fullscreen": "フルスクリーン切り替え", 21 | "Language": "言語", 22 | "Window": "窓", 23 | "Minimize": "最小化", 24 | "Zoom": "ズーム", 25 | "Close": "閉じる", 26 | "Help": "助けて", 27 | "Learn More": "もっと詳しく知る" 28 | } -------------------------------------------------------------------------------- /app/src/core/root.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { HistoryRouter } from "redux-first-history/rr6"; 3 | import { Provider } from "react-redux"; 4 | import AppRoutes from "Core/routes"; 5 | import Nav from "./nav"; 6 | import "./root.css"; 7 | 8 | class Root extends React.Component { 9 | render() { 10 | const { store, history } = this.props; 11 | 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | } 24 | 25 | export default Root; 26 | -------------------------------------------------------------------------------- /app/localization/locales/am/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "እው ሰላም ነው", 3 | "File": "ፋይል", 4 | "Exit": "መውጫ", 5 | "Edit": "አርትዕ", 6 | "Undo": "ቀልብስ", 7 | "Redo": "ድገም", 8 | "Cut": "ቁረጥ", 9 | "Copy": "ገልብጥ", 10 | "Paste": "ለጥፍ", 11 | "Delete": "ሰርዝ", 12 | "Select All": "ሁሉንም ምረጥ", 13 | "View": "አሳይ", 14 | "Reload": "እንደገና ጫን", 15 | "Force Reload": "አስገዳጅ ዳግም ጫን", 16 | "Toggle Developer Tools": "የገንቢ መሣሪያዎችን ይቀያይሩ", 17 | "Reset Zoom": "አጉላ ዳግም አስጀምር", 18 | "Zoom In": "አቅርብ", 19 | "Zoom Out": "አጉላ", 20 | "Toggle Fullscreen": "ሙሉ ማያ ገጽን ይቀያይሩ", 21 | "Language": "ቋንቋ", 22 | "Window": "መስኮት", 23 | "Minimize": "አሳንስ", 24 | "Zoom": "አጉላ", 25 | "Close": "ገጠመ", 26 | "Help": "እገዛ", 27 | "Learn More": "ተጨማሪ እወቅ" 28 | } -------------------------------------------------------------------------------- /app/src/pages/localization/localization.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withTranslation } from "react-i18next"; 3 | import "./localization.css"; 4 | 5 | class Localization extends React.Component { 6 | render() { 7 | const { t } = this.props; 8 | return ( 9 | 10 |
11 |
12 |

{t("Hello")}

13 |
14 | Try changing the language in the menu bar! 15 |
16 |
17 |
18 |
19 | ); 20 | } 21 | } 22 | 23 | export default withTranslation()(Localization); 24 | -------------------------------------------------------------------------------- /app/localization/locales/en/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hello", 3 | "File": "File", 4 | "Exit": "Exit", 5 | "Edit": "Edit", 6 | "Undo": "Undo", 7 | "Redo": "Redo", 8 | "Cut": "Cut", 9 | "Copy": "Copy", 10 | "Paste": "Paste", 11 | "Delete": "Delete", 12 | "Select All": "Select All", 13 | "View": "View", 14 | "Reload": "Reload", 15 | "Force Reload": "Force Reload", 16 | "Toggle Developer Tools": "Toggle Developer Tools", 17 | "Reset Zoom": "Reset Zoom", 18 | "Zoom In": "Zoom In", 19 | "Zoom Out": "Zoom Out", 20 | "Toggle Fullscreen": "Toggle Fullscreen", 21 | "Language": "Language", 22 | "Window": "Window", 23 | "Minimize": "Minimize", 24 | "Zoom": "Zoom", 25 | "Close": "Close", 26 | "Help": "Help", 27 | "Learn More": "Learn More" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/ar/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "مرحبا", 3 | "File": "ملف", 4 | "Exit": "خروج", 5 | "Edit": "تعديل", 6 | "Undo": "الغاء التحميل", 7 | "Redo": "إعادة", 8 | "Cut": "يقطع", 9 | "Copy": "نسخ", 10 | "Paste": "معجون", 11 | "Delete": "حذف", 12 | "Select All": "اختر الكل", 13 | "View": "رأي", 14 | "Reload": "إعادة تحميل", 15 | "Force Reload": "فرض إعادة التحميل", 16 | "Toggle Developer Tools": "تبديل أدوات المطور", 17 | "Reset Zoom": "إعادة تعيين التكبير", 18 | "Zoom In": "تكبير", 19 | "Zoom Out": "تصغير", 20 | "Toggle Fullscreen": "ملء الشاشة تبديل", 21 | "Language": "لغة", 22 | "Window": "نافذة او شباك", 23 | "Minimize": "تصغير", 24 | "Zoom": "تكبير", 25 | "Close": "قريب", 26 | "Help": "مساعدة", 27 | "Learn More": "أعرف أكثر" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/sv/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hej", 3 | "File": "Fil", 4 | "Exit": "Utgång", 5 | "Edit": "Redigera", 6 | "Undo": "Ångra", 7 | "Redo": "Göra om", 8 | "Cut": "Skära", 9 | "Copy": "Kopiera", 10 | "Paste": "Klistra", 11 | "Delete": "Radera", 12 | "Select All": "Välj alla", 13 | "View": "Se", 14 | "Reload": "Ladda om", 15 | "Force Reload": "Tvinga om laddning", 16 | "Toggle Developer Tools": "Växla utvecklarverktyg", 17 | "Reset Zoom": "Återställ zoom", 18 | "Zoom In": "Zooma in", 19 | "Zoom Out": "Zooma ut", 20 | "Toggle Fullscreen": "Växla helskärm", 21 | "Language": "Språk", 22 | "Window": "Fönster", 23 | "Minimize": "Minimera", 24 | "Zoom": "Zoom", 25 | "Close": "Stänga", 26 | "Help": "Hjälp", 27 | "Learn More": "Läs mer" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/th/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "สวัสดี", 3 | "File": "ไฟล์", 4 | "Exit": "ออก", 5 | "Edit": "แก้ไข", 6 | "Undo": "เลิกทำ", 7 | "Redo": "ทำซ้ำ", 8 | "Cut": "ตัด", 9 | "Copy": "สำเนา", 10 | "Paste": "วาง", 11 | "Delete": "ลบ", 12 | "Select All": "เลือกทั้งหมด", 13 | "View": "ดู", 14 | "Reload": "โหลดซ้ำ", 15 | "Force Reload": "บังคับให้โหลดซ้ำ", 16 | "Toggle Developer Tools": "สลับเครื่องมือสำหรับนักพัฒนา", 17 | "Reset Zoom": "รีเซ็ตการซูม", 18 | "Zoom In": "ขยายเข้า", 19 | "Zoom Out": "ซูมออก", 20 | "Toggle Fullscreen": "สลับเต็มหน้าจอ", 21 | "Language": "ภาษา", 22 | "Window": "หน้าต่าง", 23 | "Minimize": "ย่อเล็กสุด", 24 | "Zoom": "ซูม", 25 | "Close": "ปิด", 26 | "Help": "ช่วยด้วย", 27 | "Learn More": "เรียนรู้เพิ่มเติม" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/da/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hej", 3 | "File": "Fil", 4 | "Exit": "Afslut", 5 | "Edit": "Redigere", 6 | "Undo": "Fortryd", 7 | "Redo": "Gentag igen", 8 | "Cut": "Skære", 9 | "Copy": "Kopi", 10 | "Paste": "sæt ind", 11 | "Delete": "Slet", 12 | "Select All": "Vælg alle", 13 | "View": "Udsigt", 14 | "Reload": "Genindlæs", 15 | "Force Reload": "Tving genindlæsning", 16 | "Toggle Developer Tools": "Skift udviklerværktøjer", 17 | "Reset Zoom": "Nulstil zoom", 18 | "Zoom In": "Zoom ind", 19 | "Zoom Out": "Zoome ud", 20 | "Toggle Fullscreen": "Skift fuld skærm", 21 | "Language": "Sprog", 22 | "Window": "Vindue", 23 | "Minimize": "Minimer", 24 | "Zoom": "Zoom", 25 | "Close": "Tæt", 26 | "Help": "Hjælp", 27 | "Learn More": "Lær mere" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/no/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hallo", 3 | "File": "Fil", 4 | "Exit": "Exit", 5 | "Edit": "Redigere", 6 | "Undo": "Angre", 7 | "Redo": "Gjøre om", 8 | "Cut": "Kutte opp", 9 | "Copy": "Kopiere", 10 | "Paste": "Lim inn", 11 | "Delete": "Slett", 12 | "Select All": "Velg alle", 13 | "View": "Utsikt", 14 | "Reload": "Last inn på nytt", 15 | "Force Reload": "Force Reload", 16 | "Toggle Developer Tools": "Bytt utviklerverktøy", 17 | "Reset Zoom": "Tilbakestill zoom", 18 | "Zoom In": "Zoom inn", 19 | "Zoom Out": "Zoome ut", 20 | "Toggle Fullscreen": "Bytt fullskjerm", 21 | "Language": "Språk", 22 | "Window": "Vindu", 23 | "Minimize": "Minimer", 24 | "Zoom": "Zoom", 25 | "Close": "Lukk", 26 | "Help": "Hjelp", 27 | "Learn More": "Lære mer" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/af/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "hallo", 3 | "File": "Lêer", 4 | "Exit": "Uitgang", 5 | "Edit": "Wysig", 6 | "Undo": "Ontdoen", 7 | "Redo": "Herhaal", 8 | "Cut": "Sny", 9 | "Copy": "Kopieer", 10 | "Paste": "Plak", 11 | "Delete": "Vee uit", 12 | "Select All": "Kies Alles", 13 | "View": "Beskou", 14 | "Reload": "Herlaai", 15 | "Force Reload": "Dwing herlaai", 16 | "Toggle Developer Tools": "Skakel ontwikkelaarhulpmiddels", 17 | "Reset Zoom": "Stel die zoom terug", 18 | "Zoom In": "Zoom in", 19 | "Zoom Out": "Zoom uit", 20 | "Toggle Fullscreen": "Skakel volskerm", 21 | "Language": "Taal", 22 | "Window": "Venster", 23 | "Minimize": "Minimaliseer", 24 | "Zoom": "Zoom in", 25 | "Close": "Naby", 26 | "Help": "Hulp", 27 | "Learn More": "Leer meer" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/he/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "שלום", 3 | "File": "קוֹבֶץ", 4 | "Exit": "יְצִיאָה", 5 | "Edit": "לַעֲרוֹך", 6 | "Undo": "לבטל", 7 | "Redo": "לַעֲשׂוֹת שׁוּב", 8 | "Cut": "גזירה", 9 | "Copy": "עותק", 10 | "Paste": "לְהַדבִּיק", 11 | "Delete": "לִמְחוֹק", 12 | "Select All": "בחר הכל", 13 | "View": "נוף", 14 | "Reload": "לִטעוֹן מִחָדָשׁ", 15 | "Force Reload": "כוח טעינה מחדש", 16 | "Toggle Developer Tools": "החלף את הכלים למפתחים", 17 | "Reset Zoom": "אפס זום", 18 | "Zoom In": "לְהִתְמַקֵד", 19 | "Zoom Out": "להקטין את התצוגה", 20 | "Toggle Fullscreen": "החלף מסך מלא", 21 | "Language": "שפה", 22 | "Window": "חַלוֹן", 23 | "Minimize": "לְצַמְצֵם", 24 | "Zoom": "תקריב", 25 | "Close": "סגור", 26 | "Help": "עֶזרָה", 27 | "Learn More": "למד עוד" 28 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "umd", 5 | "lib": [ 6 | "ES2015", 7 | "ES2016", 8 | "ES2017", 9 | "ES2018", 10 | "ES2019", 11 | "ES2020", 12 | "ESNext", 13 | "dom" 14 | ], 15 | "jsx": "react", 16 | "noEmit": true, 17 | "sourceMap": true, 18 | /* Strict Type-Checking Options */ 19 | "strict": true, 20 | "noImplicitAny": true, 21 | "strictNullChecks": true, 22 | /* Module Resolution Options */ 23 | "moduleResolution": "node", 24 | "forceConsistentCasingInFileNames": true, 25 | "esModuleInterop": true 26 | }, 27 | "include": [ 28 | "app/src" 29 | ] 30 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "module-resolver", 5 | { 6 | "cwd": "babelrc", 7 | "alias": { 8 | "Constants": "./app/src/constants", 9 | "Components": "./app/src/components", 10 | "Core": "./app/src/core", 11 | "Pages": "./app/src/pages", 12 | "Redux": "./app/src/redux", 13 | "I18n": "./app/localization", 14 | "Images": "./resources/images" 15 | } 16 | }, 17 | "@babel/plugin-syntax-dynamic-import" 18 | ] 19 | ], 20 | "presets": [ 21 | "@babel/preset-env", 22 | "@babel/preset-react", 23 | "@babel/preset-typescript" 24 | ] 25 | } -------------------------------------------------------------------------------- /app/localization/locales/sr/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Здраво", 3 | "File": "Филе", 4 | "Exit": "Излаз", 5 | "Edit": "Уредити", 6 | "Undo": "Опозови", 7 | "Redo": "Понови", 8 | "Cut": "Исеци", 9 | "Copy": "Копирај", 10 | "Paste": "Налепи", 11 | "Delete": "Избриши", 12 | "Select All": "Изабери све", 13 | "View": "Поглед", 14 | "Reload": "Освежи", 15 | "Force Reload": "Форце Релоад", 16 | "Toggle Developer Tools": "Укључите / искључите алате за програмере", 17 | "Reset Zoom": "Ресетујте зум", 18 | "Zoom In": "Увеличати", 19 | "Zoom Out": "Умањи", 20 | "Toggle Fullscreen": "Укључи / искључи цео екран", 21 | "Language": "Језик", 22 | "Window": "Прозор", 23 | "Minimize": "Смањите", 24 | "Zoom": "Зоом", 25 | "Close": "Близу", 26 | "Help": "Помоћ", 27 | "Learn More": "Сазнајте више" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/sw/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Habari", 3 | "File": "Faili", 4 | "Exit": "Utgång", 5 | "Edit": "Hariri", 6 | "Undo": "Tendua", 7 | "Redo": "Rudia", 8 | "Cut": "Kata", 9 | "Copy": "Nakili", 10 | "Paste": "Bandika", 11 | "Delete": "Futa", 12 | "Select All": "Chagua Zote", 13 | "View": "Angalia", 14 | "Reload": "Pakia upya", 15 | "Force Reload": "Lazimisha Kupakia tena", 16 | "Toggle Developer Tools": "Geuza Zana za Wasanidi Programu", 17 | "Reset Zoom": "Weka Upya Zoom", 18 | "Zoom In": "Vuta karibu", 19 | "Zoom Out": "Zoom nje", 20 | "Toggle Fullscreen": "Geuza Skrini nzima", 21 | "Language": "Lugha", 22 | "Window": "Dirisha", 23 | "Minimize": "Punguza", 24 | "Zoom": "Kuza", 25 | "Close": "Funga", 26 | "Help": "Msaada", 27 | "Learn More": "Jifunze zaidi" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/fi/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hei", 3 | "File": "Tiedosto", 4 | "Exit": "Lopeta", 5 | "Edit": "Muokata", 6 | "Undo": "Kumoa", 7 | "Redo": "Tee uudelleen", 8 | "Cut": "Leikata", 9 | "Copy": "Kopio", 10 | "Paste": "Liitä", 11 | "Delete": "Poistaa", 12 | "Select All": "Valitse kaikki", 13 | "View": "Näytä", 14 | "Reload": "Lataa uudelleen", 15 | "Force Reload": "Pakota uudelleen", 16 | "Toggle Developer Tools": "Toggle Kehittäjän työkalut", 17 | "Reset Zoom": "Nollaa zoomaus", 18 | "Zoom In": "Lähennä", 19 | "Zoom Out": "Loitontaa", 20 | "Toggle Fullscreen": "Vaihda Koko näyttö", 21 | "Language": "Kieli", 22 | "Window": "Ikkuna", 23 | "Minimize": "Minimoida", 24 | "Zoom": "Zoomaus", 25 | "Close": "kiinni", 26 | "Help": "auta", 27 | "Learn More": "Lisätietoja" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/gu/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "નમસ્તે", 3 | "File": "ફાઇલ", 4 | "Exit": "બહાર નીકળો", 5 | "Edit": "સંપાદિત કરો", 6 | "Undo": "પૂર્વવત્ કરો", 7 | "Redo": "ફરી કરો", 8 | "Cut": "કાપવું", 9 | "Copy": "નકલ કરો", 10 | "Paste": "પેસ્ટ કરો", 11 | "Delete": "કા .ી નાખો", 12 | "Select All": "બધા પસંદ કરો", 13 | "View": "જુઓ", 14 | "Reload": "ફરીથી લોડ", 15 | "Force Reload": "ફરીથી લોડ કરો", 16 | "Toggle Developer Tools": "ટ Developગલ વિકાસકર્તા સાધનો", 17 | "Reset Zoom": "ઝૂમ ફરીથી સેટ કરો", 18 | "Zoom In": "મોટું કરો", 19 | "Zoom Out": "ઝૂમ આઉટ", 20 | "Toggle Fullscreen": "પૂર્ણસ્ક્રીન ટogગલ કરો", 21 | "Language": "ભાષા", 22 | "Window": "વિંડો", 23 | "Minimize": "ઘટાડવા", 24 | "Zoom": "ઝૂમ", 25 | "Close": "બંધ", 26 | "Help": "સહાય કરો", 27 | "Learn More": "વધુ શીખો" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/cs/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Ahoj", 3 | "File": "Soubor", 4 | "Exit": "Výstup", 5 | "Edit": "Upravit", 6 | "Undo": "Vrátit", 7 | "Redo": "Předělat", 8 | "Cut": "Střih", 9 | "Copy": "Kopírovat", 10 | "Paste": "Vložit", 11 | "Delete": "Vymazat", 12 | "Select All": "Vybrat vše", 13 | "View": "Pohled", 14 | "Reload": "Znovu načíst", 15 | "Force Reload": "Force Reload", 16 | "Toggle Developer Tools": "Přepnout vývojářské nástroje", 17 | "Reset Zoom": "Obnovit přiblížení", 18 | "Zoom In": "Přiblížit", 19 | "Zoom Out": "Oddálit", 20 | "Toggle Fullscreen": "Přepnout na celou obrazovku", 21 | "Language": "Jazyk", 22 | "Window": "Okno", 23 | "Minimize": "Minimalizovat", 24 | "Zoom": "Zvětšení", 25 | "Close": "Zavřít", 26 | "Help": "Pomoc", 27 | "Learn More": "Zjistit více" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/fa/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "سلام", 3 | "File": "فایل", 4 | "Exit": "خروج", 5 | "Edit": "ویرایش کنید", 6 | "Undo": "واگرد", 7 | "Redo": "دوباره انجام دهید", 8 | "Cut": "قطع كردن", 9 | "Copy": "کپی 🀄", 10 | "Paste": "چسباندن", 11 | "Delete": "حذف", 12 | "Select All": "انتخاب همه", 13 | "View": "چشم انداز", 14 | "Reload": "بارگیری مجدد", 15 | "Force Reload": "بارگیری مجدد", 16 | "Toggle Developer Tools": "Toggle Developer Tools", 17 | "Reset Zoom": "تنظیم مجدد بزرگنمایی", 18 | "Zoom In": "بزرگنمایی", 19 | "Zoom Out": "کوچک نمایی", 20 | "Toggle Fullscreen": "تغییر حالت تمام صفحه", 21 | "Language": "زبان", 22 | "Window": "پنجره", 23 | "Minimize": "به حداقل رساندن", 24 | "Zoom": "بزرگنمایی", 25 | "Close": "نزدیک", 26 | "Help": "کمک", 27 | "Learn More": "بیشتر بدانید" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/mr/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "नमस्कार", 3 | "File": "फाईल", 4 | "Exit": "बाहेर पडा", 5 | "Edit": "सुधारणे", 6 | "Undo": "पूर्ववत करा", 7 | "Redo": "पुन्हा करा", 8 | "Cut": "कट", 9 | "Copy": "कॉपी करा", 10 | "Paste": "पेस्ट करा", 11 | "Delete": "हटवा", 12 | "Select All": "सर्व निवडा", 13 | "View": "पहा", 14 | "Reload": "रीलोड करा", 15 | "Force Reload": "सक्तीने रीलोड करा", 16 | "Toggle Developer Tools": "विकसक साधने टॉगल करा", 17 | "Reset Zoom": "झूम रीसेट करा", 18 | "Zoom In": "प्रतिमेचे दृष्य रूप मोठे करा", 19 | "Zoom Out": "झूम कमी करा", 20 | "Toggle Fullscreen": "टॉगल पूर्णस्क्रीन", 21 | "Language": "इंग्रजी", 22 | "Window": "विंडो", 23 | "Minimize": "कमी करा", 24 | "Zoom": "झूम करा", 25 | "Close": "बंद", 26 | "Help": "मदत", 27 | "Learn More": "अधिक जाणून घ्या" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/fr/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Bonjour", 3 | "File": "Fichier", 4 | "Exit": "Quitter", 5 | "Edit": "Éditer", 6 | "Undo": "Annuler", 7 | "Redo": "Rétablir", 8 | "Cut": "Couper", 9 | "Copy": "Copier", 10 | "Paste": "Coller", 11 | "Delete": "Effacer", 12 | "Select All": "Tout sélectionner", 13 | "View": "Vue", 14 | "Reload": "Recharger", 15 | "Force Reload": "Rechargement forcé", 16 | "Toggle Developer Tools": "Outils de développement", 17 | "Reset Zoom": "Réinitialiser le zoom", 18 | "Zoom In": "Agrandir", 19 | "Zoom Out": "Réduire", 20 | "Toggle Fullscreen": "Basculer en plein écran", 21 | "Language": "Langue", 22 | "Window": "Fenêtre", 23 | "Minimize": "Minimiser", 24 | "Zoom": "Zoom", 25 | "Close": "Fermer", 26 | "Help": "Aide", 27 | "Learn More": "En apprendre plus" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/id/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Halo", 3 | "File": "Mengajukan", 4 | "Exit": "Keluar", 5 | "Edit": "Edit", 6 | "Undo": "Membuka", 7 | "Redo": "Mengulangi", 8 | "Cut": "Memotong", 9 | "Copy": "Salinan", 10 | "Paste": "Tempel", 11 | "Delete": "Menghapus", 12 | "Select All": "Pilih Semua", 13 | "View": "Melihat", 14 | "Reload": "Muat ulang", 15 | "Force Reload": "Paksa Muat Ulang", 16 | "Toggle Developer Tools": "Alihkan Alat Pengembang", 17 | "Reset Zoom": "Setel Ulang Zoom", 18 | "Zoom In": "Perbesar", 19 | "Zoom Out": "Perkecil", 20 | "Toggle Fullscreen": "Alihkan Layar Penuh", 21 | "Language": "Bahasa", 22 | "Window": "Jendela", 23 | "Minimize": "Memperkecil", 24 | "Zoom": "Perbesar", 25 | "Close": "Menutup", 26 | "Help": "Tolong", 27 | "Learn More": "Belajarlah lagi" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/ms/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hello", 3 | "File": "Fail", 4 | "Exit": "Keluar", 5 | "Edit": "Edit", 6 | "Undo": "Buat asal", 7 | "Redo": "Buat semula", 8 | "Cut": "Potong", 9 | "Copy": "Salinan", 10 | "Paste": "Tampal", 11 | "Delete": "Padam", 12 | "Select All": "Pilih semua", 13 | "View": "Pandangan", 14 | "Reload": "Tambah nilai", 15 | "Force Reload": "Pakai Muat Semula", 16 | "Toggle Developer Tools": "Togol Alat Pembangun", 17 | "Reset Zoom": "Tetapkan semula Zum", 18 | "Zoom In": "Mengezum masuk", 19 | "Zoom Out": "Zum keluar", 20 | "Toggle Fullscreen": "Togol Skrin Penuh", 21 | "Language": "Bahasa", 22 | "Window": "Tingkap", 23 | "Minimize": "Kurangkan", 24 | "Zoom": "Zum", 25 | "Close": "Tutup", 26 | "Help": "Tolonglah", 27 | "Learn More": "Ketahui Lebih Lanjut" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/et/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Tere", 3 | "File": "Fail", 4 | "Exit": "Välju", 5 | "Edit": "Muuda", 6 | "Undo": "Võta tagasi", 7 | "Redo": "Tee uuesti", 8 | "Cut": "Lõika", 9 | "Copy": "Kopeeri", 10 | "Paste": "Kleepige", 11 | "Delete": "Kustuta", 12 | "Select All": "Vali kõik", 13 | "View": "Vaade", 14 | "Reload": "Laadige uuesti", 15 | "Force Reload": "Sundige uuesti laadima", 16 | "Toggle Developer Tools": "Lülitage arendaja tööriistad sisse ja välja", 17 | "Reset Zoom": "Lähtesta suum", 18 | "Zoom In": "Suurenda", 19 | "Zoom Out": "Suumi välja", 20 | "Toggle Fullscreen": "Lülitab täisekraani sisse", 21 | "Language": "Keel", 22 | "Window": "Aken", 23 | "Minimize": "Minimeeri", 24 | "Zoom": "Suum", 25 | "Close": "Sulge", 26 | "Help": "Abi", 27 | "Learn More": "Lisateave" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/hi/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "नमस्कार", 3 | "File": "फ़ाइल", 4 | "Exit": "बाहर जाएं", 5 | "Edit": "संपादित करें", 6 | "Undo": "पूर्ववत करें", 7 | "Redo": "फिर से करें", 8 | "Cut": "कट गया", 9 | "Copy": "कॉपी करें", 10 | "Paste": "चिपकाना", 11 | "Delete": "हटा दें", 12 | "Select All": "सबका चयन करें", 13 | "View": "राय", 14 | "Reload": "पुनः लोड करें", 15 | "Force Reload": "फोर्स रीलोड", 16 | "Toggle Developer Tools": "डेवलपर टूल टॉगल करें", 17 | "Reset Zoom": "ज़ूम रीसेट करें", 18 | "Zoom In": "ज़ूम इन", 19 | "Zoom Out": "ज़ूम आउट", 20 | "Toggle Fullscreen": "पूर्णस्क्रीन चालू करें", 21 | "Language": "भाषा: हिन्दी", 22 | "Window": "खिड़की", 23 | "Minimize": "छोटा करना", 24 | "Zoom": "ज़ूम", 25 | "Close": "बंद करे", 26 | "Help": "हाथ बटाना", 27 | "Learn More": "और अधिक जानें" 28 | } -------------------------------------------------------------------------------- /dev-scripts/prepareDevServer.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { 3 | exec 4 | } = require("child_process"); 5 | const logFilePath = "./dev-scripts/webpack-dev-server.log"; 6 | const errorLogFilePath = "./dev-scripts/webpack-dev-server-error.log"; 7 | 8 | console.log(`Preparing webpack development server. (Logging webpack output to '${logFilePath}')`); 9 | 10 | // Delete the old webpack-dev-server.log if it is present 11 | try { 12 | fs.unlinkSync(logFilePath); 13 | } catch (error) { 14 | // Existing webpack-dev-server log file may not exist 15 | } 16 | 17 | // Delete the old webpack-dev-server-error.log if it is present 18 | try { 19 | fs.unlinkSync(errorLogFilePath); 20 | } catch (error) { 21 | // Existing webpack-dev-server-error log file may not exist 22 | } 23 | 24 | // Start the webpack development server 25 | exec("npm run dev-server"); -------------------------------------------------------------------------------- /app/localization/locales/lv/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Sveiki", 3 | "File": "Fails", 4 | "Exit": "Izeja", 5 | "Edit": "Rediģēt", 6 | "Undo": "Atsaukt", 7 | "Redo": "Pārtaisīt", 8 | "Cut": "Griezt", 9 | "Copy": "Kopēt", 10 | "Paste": "Ielīmēt", 11 | "Delete": "Dzēst", 12 | "Select All": "Izvēlēties visus", 13 | "View": "Skats", 14 | "Reload": "Pārlādēt", 15 | "Force Reload": "Piespiest pārlādēt", 16 | "Toggle Developer Tools": "Pārslēgt izstrādātāja rīkus", 17 | "Reset Zoom": "Atiestatīt tālummaiņu", 18 | "Zoom In": "Pietuvināt", 19 | "Zoom Out": "Attālināt", 20 | "Toggle Fullscreen": "Pārslēgt pilnekrāna režīmu", 21 | "Language": "Valoda", 22 | "Window": "Logs", 23 | "Minimize": "Samazināt", 24 | "Zoom": "Tālummaiņa", 25 | "Close": "Aizvērt", 26 | "Help": "Palīdzība", 27 | "Learn More": "Uzzināt vairāk" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/vi/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "xin chào", 3 | "File": "Tập tin", 4 | "Exit": "Lối ra", 5 | "Edit": "Biên tập", 6 | "Undo": "Hoàn tác", 7 | "Redo": "Làm lại", 8 | "Cut": "Cắt", 9 | "Copy": "Sao chép", 10 | "Paste": "Dán", 11 | "Delete": "Xóa bỏ", 12 | "Select All": "Chọn tất cả", 13 | "View": "Lượt xem", 14 | "Reload": "Nạp lại", 15 | "Force Reload": "Buộc tải lại", 16 | "Toggle Developer Tools": "Chuyển đổi công cụ dành cho nhà phát triển", 17 | "Reset Zoom": "Đặt lại Thu phóng", 18 | "Zoom In": "Phóng to", 19 | "Zoom Out": "Thu nhỏ", 20 | "Toggle Fullscreen": "Bật chế độ toàn màn hình", 21 | "Language": "Ngôn ngữ", 22 | "Window": "Cửa sổ", 23 | "Minimize": "Giảm thiểu", 24 | "Zoom": "Thu phóng", 25 | "Close": "Đóng", 26 | "Help": "Cứu giúp", 27 | "Learn More": "Tìm hiểu thêm" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/ca/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hola", 3 | "File": "Dossier", 4 | "Exit": "Surt", 5 | "Edit": "Edita", 6 | "Undo": "Desfés", 7 | "Redo": "Refés", 8 | "Cut": "Tallar", 9 | "Copy": "Copia", 10 | "Paste": "Enganxa", 11 | "Delete": "Suprimeix", 12 | "Select All": "Seleccionar tot", 13 | "View": "Veure", 14 | "Reload": "Recarregar", 15 | "Force Reload": "Força la recàrrega", 16 | "Toggle Developer Tools": "Commuta les eines per a desenvolupadors", 17 | "Reset Zoom": "Restableix el zoom", 18 | "Zoom In": "Ampliar", 19 | "Zoom Out": "Disminuir el zoom", 20 | "Toggle Fullscreen": "Commuta la pantalla completa", 21 | "Language": "Llenguatge", 22 | "Window": "Finestra", 23 | "Minimize": "Minimitzar", 24 | "Zoom": "Zoom", 25 | "Close": "Tanca", 26 | "Help": "Ajuda", 27 | "Learn More": "Aprèn més" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/es/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hola", 3 | "File": "Archivo", 4 | "Exit": "Salida", 5 | "Edit": "Editar", 6 | "Undo": "Deshacer", 7 | "Redo": "Rehacer", 8 | "Cut": "Cortar", 9 | "Copy": "Copiar", 10 | "Paste": "Pegar", 11 | "Delete": "Eliminar", 12 | "Select All": "Seleccionar todo", 13 | "View": "Ver", 14 | "Reload": "Recargar", 15 | "Force Reload": "Forzar recarga", 16 | "Toggle Developer Tools": "Alternar herramientas para desarrolladores", 17 | "Reset Zoom": "Restablecer zoom", 18 | "Zoom In": "Acercarse", 19 | "Zoom Out": "Disminuir el zoom", 20 | "Toggle Fullscreen": "Alternar pantalla completa", 21 | "Language": "Idioma", 22 | "Window": "Ventana", 23 | "Minimize": "Minimizar", 24 | "Zoom": "Enfocar", 25 | "Close": "Cerrar", 26 | "Help": "Ayuda", 27 | "Learn More": "Aprende más" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/pt/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Olá", 3 | "File": "Arquivo", 4 | "Exit": "Saída", 5 | "Edit": "Editar", 6 | "Undo": "Desfazer", 7 | "Redo": "Refazer", 8 | "Cut": "Cortar", 9 | "Copy": "cópia de", 10 | "Paste": "Colar", 11 | "Delete": "Excluir", 12 | "Select All": "Selecionar tudo", 13 | "View": "Visão", 14 | "Reload": "recarregar", 15 | "Force Reload": "Forçar recarga", 16 | "Toggle Developer Tools": "Alternar ferramentas de desenvolvedor", 17 | "Reset Zoom": "Redefinir zoom", 18 | "Zoom In": "Mais Zoom", 19 | "Zoom Out": "Reduzir o zoom", 20 | "Toggle Fullscreen": "Alternar para o modo tela cheia", 21 | "Language": "Língua", 22 | "Window": "Janela", 23 | "Minimize": "Minimizar", 24 | "Zoom": "Ampliação", 25 | "Close": "Perto", 26 | "Help": "Socorro", 27 | "Learn More": "Saber mais" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/ro/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "buna", 3 | "File": "Fişier", 4 | "Exit": "Ieșire", 5 | "Edit": "Editați | ×", 6 | "Undo": "Anula", 7 | "Redo": "A reface", 8 | "Cut": "A tăia", 9 | "Copy": "Copie", 10 | "Paste": "Pastă", 11 | "Delete": "Șterge", 12 | "Select All": "Selectează tot", 13 | "View": "Vedere", 14 | "Reload": "Reîncarcă", 15 | "Force Reload": "Forța de reîncărcare", 16 | "Toggle Developer Tools": "Comutați instrumentele pentru dezvoltatori", 17 | "Reset Zoom": "Resetați Zoom", 18 | "Zoom In": "A mari", 19 | "Zoom Out": "A micsora", 20 | "Toggle Fullscreen": "Comutare la ecran complet", 21 | "Language": "Limba", 22 | "Window": "Fereastră", 23 | "Minimize": "Minimizează", 24 | "Zoom": "Zoom", 25 | "Close": "Închide", 26 | "Help": "Ajutor", 27 | "Learn More": "Aflați mai multe" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/fil/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Kamusta", 3 | "File": "File", 4 | "Exit": "Lumabas", 5 | "Edit": "I-edit", 6 | "Undo": "Pawalang-bisa", 7 | "Redo": "Gawing muli", 8 | "Cut": "Gupitin", 9 | "Copy": "Kopya", 10 | "Paste": "I-paste", 11 | "Delete": "Tanggalin", 12 | "Select All": "Piliin lahat", 13 | "View": "Tingnan", 14 | "Reload": "Reload", 15 | "Force Reload": "Force Reload", 16 | "Toggle Developer Tools": "I-toggle ang Mga Tool ng Developer", 17 | "Reset Zoom": "I-reset ang Pag-zoom", 18 | "Zoom In": "Palakihin", 19 | "Zoom Out": "Mag-zoom Out", 20 | "Toggle Fullscreen": "I-toggle ang Fullscreen", 21 | "Language": "Wika", 22 | "Window": "Window", 23 | "Minimize": "I-minimize", 24 | "Zoom": "Mag-zoom", 25 | "Close": "Isara", 26 | "Help": "Tulong", 27 | "Learn More": "Matuto Nang Higit Pa" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/kn/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "ಹಲೋ", 3 | "File": "ಫೈಲ್", 4 | "Exit": "ನಿರ್ಗಮಿಸಿ", 5 | "Edit": "ತಿದ್ದು", 6 | "Undo": "ರದ್ದುಗೊಳಿಸಿ", 7 | "Redo": "ಮತ್ತೆಮಾಡು", 8 | "Cut": "ಕತ್ತರಿಸಿ", 9 | "Copy": "ನಕಲಿಸಿ", 10 | "Paste": "ಅಂಟಿಸಿ", 11 | "Delete": "ಅಳಿಸಿ", 12 | "Select All": "ಎಲ್ಲವನ್ನು ಆರಿಸು", 13 | "View": "ನೋಟ", 14 | "Reload": "ಮರುಲೋಡ್ ಮಾಡಿ", 15 | "Force Reload": "ಫೋರ್ಸ್ ರೀಲೋಡ್", 16 | "Toggle Developer Tools": "ಡೆವಲಪರ್ ಪರಿಕರಗಳನ್ನು ಟಾಗಲ್ ಮಾಡಿ", 17 | "Reset Zoom": "ಜೂಮ್ ಅನ್ನು ಮರುಹೊಂದಿಸಿ", 18 | "Zoom In": "ಇನ್ನು ಹತ್ತಿರವಾಗಿಸಿ", 19 | "Zoom Out": "O ೂಮ್ .ಟ್ ಮಾಡಿ", 20 | "Toggle Fullscreen": "ಪೂರ್ಣಪರದೆ ಟಾಗಲ್ ಮಾಡಿ", 21 | "Language": "ಭಾಷೆ", 22 | "Window": "ಕಿಟಕಿ", 23 | "Minimize": "ಕಡಿಮೆ ಮಾಡಿ", 24 | "Zoom": "O ೂಮ್ ಮಾಡಿ", 25 | "Close": "ಮುಚ್ಚಿ", 26 | "Help": "ಸಹಾಯ", 27 | "Learn More": "ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/ru/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Здравствуйте", 3 | "File": "Файл", 4 | "Exit": "Выход", 5 | "Edit": "Редактировать", 6 | "Undo": "Отменить", 7 | "Redo": "Повторить", 8 | "Cut": "Вырезать", 9 | "Copy": "Копировать", 10 | "Paste": "Вставить", 11 | "Delete": "Удалить", 12 | "Select All": "Выбрать все", 13 | "View": "Вид", 14 | "Reload": "Перезагрузить", 15 | "Force Reload": "Перезагрузить принудительно", 16 | "Toggle Developer Tools": "Инструменты разработчика", 17 | "Reset Zoom": "Сбросить масштаб", 18 | "Zoom In": "Приблизить", 19 | "Zoom Out": "Уменьшить", 20 | "Toggle Fullscreen": "Полноэкранный режим", 21 | "Language": "Язык", 22 | "Window": "Окно", 23 | "Minimize": "Свернуть", 24 | "Zoom": "Развернуть", 25 | "Close": "Закрыть", 26 | "Help": "Помощь", 27 | "Learn More": "Узнать больше" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/uk/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Вітаємо", 3 | "File": "Файл", 4 | "Exit": "Вихід", 5 | "Edit": "Редагувати", 6 | "Undo": "Скасувати", 7 | "Redo": "Повторити", 8 | "Cut": "Вирізати", 9 | "Copy": "Копіювати", 10 | "Paste": "Вставити", 11 | "Delete": "Видалити", 12 | "Select All": "Вибрати все", 13 | "View": "Вигляд", 14 | "Reload": "Перезавантажити", 15 | "Force Reload": "Перезавантажити примусово", 16 | "Toggle Developer Tools": "Інструменти розробника", 17 | "Reset Zoom": "Скинути масштаб", 18 | "Zoom In": "Збільшити", 19 | "Zoom Out": "Зменшити", 20 | "Toggle Fullscreen": "Повноекранний режим", 21 | "Language": "Мова", 22 | "Window": "Вікно", 23 | "Minimize": "Мінімізувати", 24 | "Zoom": "Розвернути", 25 | "Close": "Закрити", 26 | "Help": "Допомога", 27 | "Learn More": "Дізнатися більше" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/nl/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hallo", 3 | "File": "het dossier", 4 | "Exit": "Uitgang", 5 | "Edit": "Bewerk", 6 | "Undo": "Ongedaan maken", 7 | "Redo": "Opnieuw doen", 8 | "Cut": "Besnoeiing", 9 | "Copy": "Kopiëren", 10 | "Paste": "Plakken", 11 | "Delete": "Verwijderen", 12 | "Select All": "Selecteer alles", 13 | "View": "Visie", 14 | "Reload": "Herlaad", 15 | "Force Reload": "Forceer herladen", 16 | "Toggle Developer Tools": "Schakel Developer Tools in", 17 | "Reset Zoom": "Zoom opnieuw instellen", 18 | "Zoom In": "In zoomen", 19 | "Zoom Out": "Uitzoomen", 20 | "Toggle Fullscreen": "Volledig scherm activeren", 21 | "Language": "Taal", 22 | "Window": "Venster", 23 | "Minimize": "Minimaliseren", 24 | "Zoom": "Zoom", 25 | "Close": "Dichtbij", 26 | "Help": "Helpen", 27 | "Learn More": "Leer meer" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/sk/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Ahoj", 3 | "File": "Súbor", 4 | "Exit": "Východ", 5 | "Edit": "Upraviť", 6 | "Undo": "Vrátenie späť", 7 | "Redo": "Znova", 8 | "Cut": "Vystrihnúť", 9 | "Copy": "Kópia", 10 | "Paste": "Vložiť", 11 | "Delete": "Odstrániť", 12 | "Select All": "Vybrať všetko", 13 | "View": "vyhliadka", 14 | "Reload": "Znova načítať", 15 | "Force Reload": "Vynútiť opätovné načítanie", 16 | "Toggle Developer Tools": "Prepnúť Nástroje pre vývojárov", 17 | "Reset Zoom": "Obnoviť priblíženie", 18 | "Zoom In": "Priblížiť", 19 | "Zoom Out": "Oddialiť", 20 | "Toggle Fullscreen": "Prepnúť na celú obrazovku", 21 | "Language": "Jazyk", 22 | "Window": "Okno", 23 | "Minimize": "Minimalizovať", 24 | "Zoom": "Zväčšiť", 25 | "Close": "Zavrieť", 26 | "Help": "Pomoc", 27 | "Learn More": "Uč sa viac" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/tr/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Merhaba", 3 | "File": "Dosya", 4 | "Exit": "çıkış", 5 | "Edit": "Düzenle", 6 | "Undo": "Geri alma", 7 | "Redo": "Yeniden yap", 8 | "Cut": "Kesmek", 9 | "Copy": "Kopyala", 10 | "Paste": "Yapıştırmak", 11 | "Delete": "Sil", 12 | "Select All": "Hepsini seç", 13 | "View": "Görünüm", 14 | "Reload": "Tekrar yükle", 15 | "Force Reload": "Yeniden Yüklemeye Zorla", 16 | "Toggle Developer Tools": "Geliştirici Araçlarını Aç / Kapat", 17 | "Reset Zoom": "Yakınlaştırmayı Sıfırla", 18 | "Zoom In": "Yakınlaştır", 19 | "Zoom Out": "Uzaklaştır", 20 | "Toggle Fullscreen": "Tam ekrana geç", 21 | "Language": "Dil", 22 | "Window": "Pencere", 23 | "Minimize": "küçültmek", 24 | "Zoom": "Yakınlaştır", 25 | "Close": "Kapat", 26 | "Help": "Yardım", 27 | "Learn More": "Daha fazla bilgi edin" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/de/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Hallo", 3 | "File": "Datei", 4 | "Exit": "Ausgang", 5 | "Edit": "Bearbeiten", 6 | "Undo": "Rückgängig machen", 7 | "Redo": "Wiederholen", 8 | "Cut": "Schnitt", 9 | "Copy": "Kopieren", 10 | "Paste": "Einfügen", 11 | "Delete": "Löschen", 12 | "Select All": "Wählen Sie Alle", 13 | "View": "Aussicht", 14 | "Reload": "Neu laden", 15 | "Force Reload": "Nachladen erzwingen", 16 | "Toggle Developer Tools": "Entwickler-Tools umschalten", 17 | "Reset Zoom": "Zoom zurücksetzen", 18 | "Zoom In": "Hineinzoomen", 19 | "Zoom Out": "Rauszoomen", 20 | "Toggle Fullscreen": "Vollbild umschalten", 21 | "Language": "Sprache", 22 | "Window": "Fenster", 23 | "Minimize": "Minimieren", 24 | "Zoom": "Zoomen", 25 | "Close": "Schließen", 26 | "Help": "Hilfe", 27 | "Learn More": "Erfahren Sie mehr" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/hu/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Helló", 3 | "File": "File", 4 | "Exit": "Kijárat", 5 | "Edit": "Szerkesztés", 6 | "Undo": "Visszavonás", 7 | "Redo": "Újra", 8 | "Cut": "Vágott", 9 | "Copy": "Másolat", 10 | "Paste": "Paszta", 11 | "Delete": "Töröl", 12 | "Select All": "Mindet kiválaszt", 13 | "View": "Kilátás", 14 | "Reload": "Újratöltés", 15 | "Force Reload": "Újratöltés kényszerítése", 16 | "Toggle Developer Tools": "Toggle Fejlesztői eszközök", 17 | "Reset Zoom": "A nagyítás visszaállítása", 18 | "Zoom In": "Ráközelíteni", 19 | "Zoom Out": "Kicsinyítés", 20 | "Toggle Fullscreen": "Teljes képernyőre váltás", 21 | "Language": "Nyelv", 22 | "Window": "Ablak", 23 | "Minimize": "Minimalizálja", 24 | "Zoom": "Zoomolás", 25 | "Close": "Bezárás", 26 | "Help": "Segítség", 27 | "Learn More": "Tudj meg többet" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/lt/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Sveiki", 3 | "File": "Failas", 4 | "Exit": "Išeiti", 5 | "Edit": "Redaguoti", 6 | "Undo": "Atšaukti", 7 | "Redo": "Perdaryti", 8 | "Cut": "Iškirpti", 9 | "Copy": "Kopijuoti", 10 | "Paste": "Įklijuoti", 11 | "Delete": "Ištrinti", 12 | "Select All": "Pasirinkti viską", 13 | "View": "Vaizdas", 14 | "Reload": "Perkrauti", 15 | "Force Reload": "Priversti perkrauti", 16 | "Toggle Developer Tools": "Perjungti kūrėjo įrankius", 17 | "Reset Zoom": "Atstatyti mastelį", 18 | "Zoom In": "Priartinti", 19 | "Zoom Out": "Nutolinti", 20 | "Toggle Fullscreen": "Įjungti pilnojo ekrano režimą", 21 | "Language": "Kalba", 22 | "Window": "Langas", 23 | "Minimize": "Sumažinkite", 24 | "Zoom": "Mastelis", 25 | "Close": "Uždaryti", 26 | "Help": "Pagalba", 27 | "Learn More": "Sužinokite daugiau" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/pl/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Witaj", 3 | "File": "Plik", 4 | "Exit": "Wyjście", 5 | "Edit": "Edytować", 6 | "Undo": "Cofnij", 7 | "Redo": "Przerobić", 8 | "Cut": "Skaleczenie", 9 | "Copy": "Kopiuj", 10 | "Paste": "Pasta", 11 | "Delete": "Usunąć", 12 | "Select All": "Zaznacz wszystko", 13 | "View": "Widok", 14 | "Reload": "Przeładować", 15 | "Force Reload": "Wymuś przeładowanie", 16 | "Toggle Developer Tools": "Przełącz narzędzia programistyczne", 17 | "Reset Zoom": "Resetuj powiększenie", 18 | "Zoom In": "Zbliżenie", 19 | "Zoom Out": "Pomniejsz", 20 | "Toggle Fullscreen": "Przełącz tryb pełnoekranowy", 21 | "Language": "Język", 22 | "Window": "Okno", 23 | "Minimize": "Zminimalizować", 24 | "Zoom": "Powiększenie", 25 | "Close": "Blisko", 26 | "Help": "Wsparcie", 27 | "Learn More": "Ucz się więcej" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/ta/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "வணக்கம்", 3 | "File": "கோப்பு", 4 | "Exit": "வெளியேறு", 5 | "Edit": "தொகு", 6 | "Undo": "செயல்தவிர்", 7 | "Redo": "மீண்டும் செய்", 8 | "Cut": "வெட்டு", 9 | "Copy": "நகலெடுக்கவும்", 10 | "Paste": "ஒட்டவும்", 11 | "Delete": "அழி", 12 | "Select All": "அனைத்தையும் தெரிவுசெய்", 13 | "View": "காண்க", 14 | "Reload": "ஏற்றவும்", 15 | "Force Reload": "ஃபோர்ஸ் ரீலோட்", 16 | "Toggle Developer Tools": "டெவலப்பர் கருவிகளை நிலைமாற்று", 17 | "Reset Zoom": "பெரிதாக்கு என்பதை மீட்டமைக்கவும்", 18 | "Zoom In": "பெரிதாக்க", 19 | "Zoom Out": "பெரிதாக்கு", 20 | "Toggle Fullscreen": "மாற்று முழுத்திரை", 21 | "Language": "மொழி", 22 | "Window": "ஜன்னல்", 23 | "Minimize": "குறைத்தல்", 24 | "Zoom": "பெரிதாக்கு", 25 | "Close": "நெருக்கமான", 26 | "Help": "உதவி", 27 | "Learn More": "மேலும் அறிக" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/te/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "హలో", 3 | "File": "ఫైల్", 4 | "Exit": "బయటకి దారి", 5 | "Edit": "సవరించండి", 6 | "Undo": "చర్యరద్దు చేయండి", 7 | "Redo": "పునరావృతం", 8 | "Cut": "కట్", 9 | "Copy": "కాపీ", 10 | "Paste": "అతికించండి", 11 | "Delete": "తొలగించు", 12 | "Select All": "అన్ని ఎంచుకోండి", 13 | "View": "చూడండి", 14 | "Reload": "రీలోడ్ చేయండి", 15 | "Force Reload": "ఫోర్స్ రీలోడ్", 16 | "Toggle Developer Tools": "డెవలపర్ సాధనాలను టోగుల్ చేయండి", 17 | "Reset Zoom": "జూమ్‌ను రీసెట్ చేయండి", 18 | "Zoom In": "పెద్దదిగా చూపు", 19 | "Zoom Out": "పెద్దది చెయ్యి", 20 | "Toggle Fullscreen": "టోగుల్ పూర్తి స్క్రీన్", 21 | "Language": "భాష", 22 | "Window": "కిటికీ", 23 | "Minimize": "తగ్గించడానికి", 24 | "Zoom": "జూమ్ చేయండి", 25 | "Close": "దగ్గరగా", 26 | "Help": "సహాయం", 27 | "Learn More": "ఇంకా నేర్చుకో" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/bg/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Здравейте", 3 | "File": "Файл", 4 | "Exit": "Изход", 5 | "Edit": "Pедактиране", 6 | "Undo": "Отмяна", 7 | "Redo": "Повторно", 8 | "Cut": "Разрез", 9 | "Copy": "копие", 10 | "Paste": "Поставете", 11 | "Delete": "Изтрий", 12 | "Select All": "Избери всички", 13 | "View": "Изглед", 14 | "Reload": "Презаредете", 15 | "Force Reload": "Принудително презареждане", 16 | "Toggle Developer Tools": "Превключете Инструменти за програмисти", 17 | "Reset Zoom": "Нулирайте мащаба", 18 | "Zoom In": "Увеличавам", 19 | "Zoom Out": "Отдалечавам", 20 | "Toggle Fullscreen": "Превключване на цял екран", 21 | "Language": "Език", 22 | "Window": "Прозорец", 23 | "Minimize": "Минимизиране", 24 | "Zoom": "Мащабиране", 25 | "Close": "Близо", 26 | "Help": "Помогне", 27 | "Learn More": "Научете повече" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/el/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Γειά σου", 3 | "File": "Αρχείο", 4 | "Exit": "Εξοδος", 5 | "Edit": "Επεξεργασία", 6 | "Undo": "Ξεκάνω", 7 | "Redo": "Ξανακάνω", 8 | "Cut": "Τομή", 9 | "Copy": "αντίγραφο", 10 | "Paste": "Επικόλληση", 11 | "Delete": "Διαγράφω", 12 | "Select All": "Επιλογή όλων", 13 | "View": "Θέα", 14 | "Reload": "Φορτώνω πάλι", 15 | "Force Reload": "Αναγκαστική επαναφόρτωση", 16 | "Toggle Developer Tools": "Εναλλαγή εργαλείων προγραμματιστή", 17 | "Reset Zoom": "Επαναφορά ζουμ", 18 | "Zoom In": "Μεγέθυνση", 19 | "Zoom Out": "Σμίκρυνση", 20 | "Toggle Fullscreen": "Λειτουργεία πλήρους οθόνης", 21 | "Language": "Γλώσσα", 22 | "Window": "Παράθυρο", 23 | "Minimize": "Σμικροποιώ", 24 | "Zoom": "Ανίπταμαι διαγωνίως", 25 | "Close": "Κλείσε", 26 | "Help": "Βοήθεια", 27 | "Learn More": "Μάθε περισσότερα" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/hr/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "zdravo", 3 | "File": "Datoteka", 4 | "Exit": "Izlaz", 5 | "Edit": "Uredi", 6 | "Undo": "Poništi", 7 | "Redo": "Ponovi", 8 | "Cut": "Izrezati", 9 | "Copy": "Kopirati", 10 | "Paste": "Zalijepiti", 11 | "Delete": "Izbrisati", 12 | "Select All": "Odaberi sve", 13 | "View": "Pogled", 14 | "Reload": "Ponovno učitati", 15 | "Force Reload": "Prisilno ponovno učitavanje", 16 | "Toggle Developer Tools": "Uključi / isključi alate za programere", 17 | "Reset Zoom": "Resetiraj zumiranje", 18 | "Zoom In": "Povećaj", 19 | "Zoom Out": "Umanji", 20 | "Toggle Fullscreen": "Uključi / isključi cijeli zaslon", 21 | "Language": "Jezik", 22 | "Window": "Prozor", 23 | "Minimize": "Minimizirajte", 24 | "Zoom": "Zum", 25 | "Close": "Zatvoriti", 26 | "Help": "Pomozite", 27 | "Learn More": "Saznajte više" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/it/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "Ciao", 3 | "File": "File", 4 | "Exit": "Uscita", 5 | "Edit": "modificare", 6 | "Undo": "Disfare", 7 | "Redo": "Rifare", 8 | "Cut": "Tagliare", 9 | "Copy": "copia", 10 | "Paste": "Incolla", 11 | "Delete": "Elimina", 12 | "Select All": "Seleziona tutto", 13 | "View": "Visualizza", 14 | "Reload": "Ricaricare", 15 | "Force Reload": "Forza ricarica", 16 | "Toggle Developer Tools": "Attiva / disattiva strumenti per sviluppatori", 17 | "Reset Zoom": "Ripristina zoom", 18 | "Zoom In": "Ingrandire", 19 | "Zoom Out": "Zoom indietro", 20 | "Toggle Fullscreen": "Passare a schermo intero", 21 | "Language": "linguaggio", 22 | "Window": "Finestra", 23 | "Minimize": "Minimizzare", 24 | "Zoom": "Ingrandisci", 25 | "Close": "Vicino", 26 | "Help": "Aiuto", 27 | "Learn More": "Per saperne di più" 28 | } -------------------------------------------------------------------------------- /app/localization/locales/ml/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello": "ഹലോ", 3 | "File": "ഫയൽ", 4 | "Exit": "പുറത്ത്", 5 | "Edit": "എഡിറ്റുചെയ്യുക", 6 | "Undo": "പഴയപടിയാക്കുക", 7 | "Redo": "വീണ്ടും ചെയ്യുക", 8 | "Cut": "മുറിക്കുക", 9 | "Copy": "പകർത്തുക", 10 | "Paste": "പേസ്റ്റ്", 11 | "Delete": "ഇല്ലാതാക്കുക", 12 | "Select All": "എല്ലാം തിരഞ്ഞെടുക്കുക", 13 | "View": "കാണുക", 14 | "Reload": "വീണ്ടും ലോഡുചെയ്യുക", 15 | "Force Reload": "ഫോഴ്‌സ് റീലോഡ്", 16 | "Toggle Developer Tools": "ഡവലപ്പർ ഉപകരണങ്ങൾ ടോഗിൾ ചെയ്യുക", 17 | "Reset Zoom": "സൂം പുന et സജ്ജമാക്കുക", 18 | "Zoom In": "വലുതാക്കുക", 19 | "Zoom Out": "സൂം .ട്ട് ചെയ്യുക", 20 | "Toggle Fullscreen": "പൂർണ്ണസ്‌ക്രീൻ ടോഗിൾ ചെയ്യുക", 21 | "Language": "ഭാഷ", 22 | "Window": "ജാലകം", 23 | "Minimize": "ചെറുതാക്കുക", 24 | "Zoom": "സൂം ചെയ്യുക", 25 | "Close": "അടയ്‌ക്കുക", 26 | "Help": "സഹായിക്കൂ", 27 | "Learn More": "കൂടുതലറിവ് നേടുക" 28 | } -------------------------------------------------------------------------------- /app/electron/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require("electron"); 2 | const fs = require("fs"); 3 | const i18nextBackend = require("i18next-electron-fs-backend"); 4 | const Store = require("secure-electron-store").default; 5 | const ContextMenu = require("secure-electron-context-menu").default; 6 | const SecureElectronLicenseKeys = require("secure-electron-license-keys"); 7 | 8 | // Create the electron store to be made available in the renderer process 9 | const store = new Store(); 10 | 11 | // Expose protected methods that allow the renderer process to use 12 | // the ipcRenderer without exposing the entire object 13 | contextBridge.exposeInMainWorld("api", { 14 | i18nextElectronBackend: i18nextBackend.preloadBindings(ipcRenderer, process), 15 | store: store.preloadBindings(ipcRenderer, fs), 16 | contextMenu: ContextMenu.preloadBindings(ipcRenderer), 17 | licenseKeys: SecureElectronLicenseKeys.preloadBindings(ipcRenderer) 18 | }); 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /app/localization/i18n.mainconfig.js: -------------------------------------------------------------------------------- 1 | const i18n = require("i18next"); 2 | const backend = require("i18next-fs-backend"); 3 | const whitelist = require("./whitelist"); 4 | 5 | // On Mac, the folder for resources isn't 6 | // in the same directory as Linux/Windows; 7 | // https://www.electron.build/configuration/contents#extrafiles 8 | const path = require("path"); 9 | const isMac = process.platform === "darwin"; 10 | const isDev = process.env.NODE_ENV === "development"; 11 | const prependPath = isMac && !isDev ? path.join(process.resourcesPath, "..") : "."; 12 | 13 | i18n 14 | .use(backend) 15 | .init({ 16 | backend: { 17 | loadPath: prependPath + "/app/localization/locales/{{lng}}/{{ns}}.json", 18 | addPath: prependPath + "/app/localization/locales/{{lng}}/{{ns}}.missing.json" 19 | }, 20 | debug: false, 21 | namespace: "translation", 22 | saveMissing: true, 23 | saveMissingTo: "current", 24 | lng: "en", 25 | fallbackLng: false, // set to false when generating translation files locally 26 | supportedLngs: whitelist.langs 27 | }); 28 | 29 | module.exports = i18n; -------------------------------------------------------------------------------- /app/src/redux/store/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | combineReducers 3 | } from "redux"; 4 | import { 5 | configureStore, 6 | getDefaultMiddleware 7 | } from "@reduxjs/toolkit"; 8 | import { 9 | createHashHistory 10 | } from "history"; 11 | import { 12 | createReduxHistoryContext 13 | } from "redux-first-history"; 14 | import undoable from "easy-redux-undo"; 15 | import homeReducer from "../components/home/homeSlice"; 16 | import counterReducer from "../components/counter/counterSlice"; 17 | import complexReducer from "../components/complex/complexSlice"; 18 | 19 | const { 20 | routerMiddleware, 21 | createReduxHistory, 22 | routerReducer 23 | } = createReduxHistoryContext({ 24 | history: createHashHistory() 25 | }); 26 | 27 | export const store = configureStore({ 28 | reducer: combineReducers({ 29 | router: routerReducer, 30 | home: homeReducer, 31 | undoable: undoable( 32 | combineReducers({ 33 | counter: counterReducer, 34 | complex: complexReducer 35 | }) 36 | ) 37 | }), 38 | middleware: [...getDefaultMiddleware({ 39 | serializableCheck: false 40 | }), routerMiddleware] 41 | }); 42 | 43 | export const history = createReduxHistory(store); -------------------------------------------------------------------------------- /app/src/components/subitem/subitem.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class SubItem extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | componentWillUnmount() { 9 | window.api.contextMenu.clearRendererBindings(); 10 | } 11 | 12 | componentDidMount() { 13 | // Set up binding in code whenever the context menu item 14 | // of id "alert" is selected 15 | window.api.contextMenu.onReceive("softAlert", function(args) { 16 | console.log(`This alert was brought to you by secure-electron-context-menu by ${args.attributes.name}`); 17 | 18 | // Note - we have access to the "params" object as defined here: https://www.electronjs.org/docs/api/web-contents#event-context-menu 19 | // args.params 20 | }, this.props.id); 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 |
30 | ID ({this.props.id}): Try right-clicking me for a custom context menu 31 |
32 |
33 | ); 34 | } 35 | } 36 | 37 | export default SubItem; 38 | -------------------------------------------------------------------------------- /app/src/redux/components/complex/complexSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const foods = ["pineapple", "kiwi", "grapes", "orange"]; 4 | const taste = ["great", "poor", "average", "good", "superb"]; 5 | 6 | const index = function(array){ 7 | return Math.floor(Math.random() * array.length); 8 | } 9 | const randomFood = function(){ 10 | return foods[index(foods)]; 11 | }; 12 | const randomTaste = function(){ 13 | return taste[index(taste)]; 14 | } 15 | 16 | const complexSlice = createSlice({ 17 | name: "complex", 18 | initialState: [{ 19 | id: 1, 20 | food: { 21 | name: "apple", 22 | taste: "great" 23 | } 24 | }], 25 | reducers: { 26 | add(state, _action) { 27 | state.push({ 28 | id: state.length + 1, 29 | food: { 30 | name: randomFood(), 31 | taste: randomTaste() 32 | } 33 | }); 34 | }, 35 | remove(state, _action) { 36 | const randIndex = Math.floor(Math.random() * state.length); 37 | state.splice(randIndex, 1); 38 | } 39 | } 40 | }); 41 | 42 | // Export actions 43 | export const { add, remove } = complexSlice.actions; 44 | 45 | // Export reducer 46 | export default complexSlice.reducer; 47 | -------------------------------------------------------------------------------- /app/src/pages/about/about.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class About extends React.Component { 4 | render() { 5 | return ( 6 |
7 |
8 |

About

9 |
10 |
11 | This template's origins arise from my work on My Budget (https://github.com/reZach/my-budget) beginning in 2019. I was building a free Electron application to manage your budget, and was doing the best I could to use and learn Electron. After I spent more time working on the project, I realized the practices I were using were not secure, and decided I needed to build an Electron template that could be used for the new (v2) budgeting application. 12 |
13 |
14 | As I began to work more and more on this template, my focus changed from building a budgeting app to making a secure electron template. Many people have offered their expertise and knowledge in making this template to the one it is today. To these people I say, thank you. I hope you make use of this template by building a wonderfully secure application! 15 |
16 |
17 | ); 18 | } 19 | } 20 | 21 | export default About; 22 | -------------------------------------------------------------------------------- /webpack.development.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const CspHtmlWebpackPlugin = require("csp-html-webpack-plugin"); 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | const { merge } = require("webpack-merge"); 5 | const base = require("./webpack.config"); 6 | const path = require("path"); 7 | 8 | module.exports = merge(base, { 9 | mode: "development", 10 | devtool: "source-map", // Show the source map so we can debug when developing locally 11 | devServer: { 12 | host: "localhost", 13 | port: "40992", 14 | hot: true, // Hot-reload this server if changes are detected 15 | compress: true, // Compress (gzip) files that are served 16 | static: { 17 | directory: path.resolve(__dirname, "app/dist"), // Where we serve the local dev server's files from 18 | watch: true, // Watch the directory for changes 19 | staticOptions: { 20 | ignored: /node_modules/ // Ignore this path, probably not needed since we define directory above 21 | } 22 | } 23 | }, 24 | plugins: [ 25 | new MiniCssExtractPlugin(), 26 | new HtmlWebpackPlugin({ 27 | template: path.resolve(__dirname, "app/src/index.html"), 28 | filename: "index.html" 29 | }), 30 | new CspHtmlWebpackPlugin({ 31 | "base-uri": ["'self'"], 32 | "object-src": ["'none'"], 33 | "script-src": ["'self'"], 34 | "style-src": ["'self'"], 35 | "frame-src": ["'none'"], 36 | "worker-src": ["'none'"] 37 | }) 38 | ] 39 | }) -------------------------------------------------------------------------------- /app/src/pages/welcome/welcome.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ROUTES from "Constants/routes"; 3 | import { Link } from "react-router-dom"; 4 | 5 | class Welcome extends React.Component { 6 | render() { 7 | return ( 8 | 9 |
10 |
11 |
12 |
13 |

14 | Thank you for trying out the secure-electron-template! 15 |

16 |

17 | Please navigate to view the features of this template. 18 |

19 |
20 |
21 |
22 |
23 |
24 |
25 |

Samples

26 |
27 | Using the Electron store.
28 | Changing locales.
29 | Undo/redoing actions.
30 | Custom context menu.
31 | Sample image loaded.
32 |
33 |
34 |
35 |
36 | ); 37 | } 38 | } 39 | 40 | export default Welcome; 41 | -------------------------------------------------------------------------------- /webpack.production.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const CspHtmlWebpackPlugin = require("csp-html-webpack-plugin"); 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); 5 | const { merge } = require("webpack-merge"); 6 | const base = require("./webpack.config"); 7 | const path = require("path"); 8 | 9 | module.exports = merge(base, { 10 | mode: "production", 11 | devtool: false, 12 | plugins: [ 13 | new MiniCssExtractPlugin(), 14 | new HtmlWebpackPlugin({ 15 | template: path.resolve(__dirname, "app/src/index.html"), 16 | filename: "index.html", 17 | base: "app://rse" 18 | }), 19 | // You can paste your CSP in this website https://csp-evaluator.withgoogle.com/ 20 | // for it to give you suggestions on how strong your CSP is 21 | new CspHtmlWebpackPlugin( 22 | { 23 | "base-uri": ["'self'"], 24 | "object-src": ["'none'"], 25 | "script-src": ["'self'"], 26 | "style-src": ["'self'"], 27 | "frame-src": ["'none'"], 28 | "worker-src": ["'none'"] 29 | }, 30 | { 31 | hashEnabled: { 32 | "style-src": false 33 | } 34 | } 35 | ) 36 | ], 37 | optimization: { 38 | minimize: true, 39 | minimizer: [ 40 | "...", // This adds default minimizers to webpack. For JS, Terser is used. // https://webpack.js.org/configuration/optimization/#optimizationminimizer 41 | new CssMinimizerPlugin() 42 | ] 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /app/localization/i18n.config.js: -------------------------------------------------------------------------------- 1 | const i18n = require("i18next"); 2 | const reactI18Next = require("react-i18next"); 3 | const i18nBackend = require("i18next-electron-fs-backend").default; 4 | const whitelist = require("./whitelist"); 5 | 6 | // On Mac, the folder for resources isn't 7 | // in the same directory as Linux/Windows; 8 | // https://www.electron.build/configuration/contents#extrafiles 9 | const isMac = window.api.i18nextElectronBackend.clientOptions.platform === "darwin"; 10 | const isDev = window.api.i18nextElectronBackend.clientOptions.environment === "development"; 11 | const prependPath = isMac && !isDev ? window.api.i18nextElectronBackend.clientOptions.resourcesPath : "."; 12 | 13 | i18n 14 | .use(i18nBackend) 15 | .use(reactI18Next.initReactI18next) 16 | .init({ 17 | backend: { 18 | loadPath: prependPath + "/app/localization/locales/{{lng}}/{{ns}}.json", 19 | addPath: prependPath + "/app/localization/locales/{{lng}}/{{ns}}.missing.json", 20 | contextBridgeApiKey: "api" // needs to match first parameter of contextBridge.exposeInMainWorld in preload file; defaults to "api" 21 | }, 22 | debug: false, 23 | namespace: "translation", 24 | saveMissing: true, 25 | saveMissingTo: "current", 26 | lng: "en", 27 | fallbackLng: false, // set to false when generating translation files locally 28 | supportedLngs: whitelist.langs 29 | }); 30 | 31 | window.api.i18nextElectronBackend.onLanguageChange((args) => { 32 | i18n.changeLanguage(args.lng, (error, _t) => { 33 | if (error) { 34 | console.error(error); 35 | } 36 | }); 37 | }); 38 | 39 | module.exports = i18n; 40 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | const Application = require("spectron").Application; 2 | const assert = require("assert"); 3 | const electronPath = require("electron"); 4 | const path = require("path"); 5 | 6 | // Sample code taken from: 7 | // https://github.com/electron-userland/spectron 8 | describe("Application launch", function () { 9 | this.timeout(10000); 10 | 11 | beforeEach(function () { 12 | this.app = new Application({ 13 | // Your electron path can be any binary 14 | // i.e for OSX an example path could be '/Applications/MyApp.app/Contents/MacOS/MyApp' 15 | // But for the sake of the example we fetch it from our node_modules. 16 | path: electronPath, 17 | 18 | // Assuming you have the following directory structure 19 | 20 | // |__ my project 21 | // |__ ... 22 | // |__ main.js 23 | // |__ package.json 24 | // |__ index.html 25 | // |__ ... 26 | // |__ test 27 | // |__ spec.js <- You are here! ~ Well you should be. 28 | 29 | // The following line tells spectron to look and use the main.js file 30 | // and the package.json located 1 level above. 31 | args: [path.join(__dirname, '..')] 32 | }) 33 | return this.app.start() 34 | }) 35 | 36 | afterEach(function () { 37 | if (this.app && this.app.isRunning()) { 38 | return this.app.stop() 39 | } 40 | }); 41 | 42 | it("shows an initial window", function () { 43 | return this.app.client.getWindowCount().then(function (count) { 44 | assert.strictEqual(count, 1); 45 | // Please note that getWindowCount() will return 2 if `dev tools` are opened. 46 | // assert.equal(count, 2) 47 | }); 48 | }); 49 | }); -------------------------------------------------------------------------------- /app/src/core/routes.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Routes, Route } from "react-router"; 3 | import ROUTES from "Constants/routes"; 4 | import loadable from "@loadable/component"; 5 | 6 | // Load bundles asynchronously so that the initial render happens faster 7 | const Welcome = loadable(() => 8 | import(/* webpackChunkName: "WelcomeChunk" */ "Pages/welcome/welcome") 9 | ); 10 | const About = loadable(() => 11 | import(/* webpackChunkName: "AboutChunk" */ "Pages/about/about") 12 | ); 13 | const Motd = loadable(() => 14 | import(/* webpackChunkName: "MotdChunk" */ "Pages/motd/motd") 15 | ); 16 | const Localization = loadable(() => 17 | import( 18 | /* webpackChunkName: "LocalizationChunk" */ "Pages/localization/localization" 19 | ) 20 | ); 21 | const UndoRedo = loadable(() => 22 | import(/* webpackChunkName: "UndoRedoChunk" */ "Pages/undoredo/undoredo") 23 | ); 24 | const ContextMenu = loadable(() => 25 | import(/* webpackChunkName: "ContextMenuChunk" */ "Pages/contextmenu/contextmenu") 26 | ); 27 | const Image = loadable(() => 28 | import(/* webpackChunkName: "ContextMenuChunk" */ "Pages/image/image") 29 | ); 30 | 31 | class AppRoutes extends React.Component { 32 | render() { 33 | return ( 34 | 35 | }> 36 | }> 37 | }> 38 | }> 39 | }> 40 | }> 41 | }> 42 | 43 | ); 44 | } 45 | } 46 | 47 | export default AppRoutes; 48 | -------------------------------------------------------------------------------- /docs/app.md: -------------------------------------------------------------------------------- 1 | # App 2 | The main location where all of your application code lives. Inside this folder are 4 sub-folders: 3 | ``` 4 | dist/ 5 | electron/ 6 | localization/ 7 | src/ 8 | ``` 9 | 10 | #### dist 11 | This folder holds bundled files from webpack. You shouldn't be modifying anything in this folder, the files contained within will be regenerated upon build (dev or production). 12 | 13 | #### electron 14 | Electron-specific files. These would be the main file that creates the window (`main.js`), the menu bar (`menu.js`) or the preload script (`preload.js`). 15 | 16 | > In the main.js file you would configure the app window and any app specific event handlers or setup IPC (inter-process-communication). The menu bar is self-explanatory so I will skip saying anything about that file. The preload.js file is where you expose, [_safely_](https://blog.doyensec.com/2019/04/03/subverting-electron-apps-via-insecure-preload.html), node symbols so that your renderer process can use them. 17 | 18 | #### localization 19 | A folder that will holds localized files of translations for your app. This setup is assuming you would be using translations offline and not call out to a webservice to translate your app. If you'd like to use another method of translating, i18next names each provider a "backend;" you can browse the list of them [here](https://www.i18next.com/overview/plugins-and-utils#backends). 20 | 21 | > Note, there are two config files in this template. **i18n.config.js** is for the renderer process (ie. front-end) and **i18n.mainconfig.js** is used for the menu items. It is important that these two config files _match_ (logically speaking, not word-for-word) so that the translation files work between menu items and the renderer process. 22 | 23 | #### src 24 | Application-specific files, these are your js/css files and everything else you are used to. A more detailed look of this directory is [here](https://github.com/reZach/secure-electron-template/blob/master/docs/src.md). -------------------------------------------------------------------------------- /app/src/pages/contextmenu/contextmenu.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SubItem from "Components/subitem/subitem"; 3 | 4 | class ContextMenu extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | 8 | this.state = { 9 | message: "", 10 | }; 11 | } 12 | 13 | componentWillUnmount() { 14 | // Clear any existing bindings; 15 | // important on mac-os if the app is suspended 16 | // and resumed. Existing subscriptions must be cleared 17 | window.api.contextMenu.clearRendererBindings(); 18 | } 19 | 20 | componentDidMount() { 21 | // Set up binding in code whenever the context menu item 22 | // of id "alert" is selected 23 | window.api.contextMenu.onReceive("loudAlert", function (args) { 24 | alert( 25 | `This alert was brought to you by secure-electron-context-menu by ${args.attributes.name}` 26 | ); 27 | 28 | // Note - we have access to the "params" object as defined here: https://www.electronjs.org/docs/api/web-contents#event-context-menu 29 | // args.params 30 | }); 31 | } 32 | 33 | render() { 34 | return ( 35 | 36 |
37 |
38 |

42 | Context menu 43 |

44 |
Right-click the header above!
45 |
46 |
47 |
48 |
49 | {/* Demonstrating how to use the context menu with multiple items */} 50 | 51 | 52 |
53 |
54 |
55 | ); 56 | } 57 | } 58 | 59 | export default ContextMenu; 60 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## How do I use the Material-UI framework in this template? 4 | Please see [this issue](https://github.com/reZach/secure-electron-template/issues/14). 5 | 6 | ## What files can I remove once I start working on my own project? 7 | If you clone secure-electron-template and want to build your own app, you'll be able to get rid of some of the repository's default files. 8 | 9 | Specifically, you can drop: 10 | * The docs folder 11 | * [At the root level, mostly config/github files] 12 | * .editorconfig 13 | * .prettierrc 14 | * CODE_OF_CONDUCT.md 15 | * README.md 16 | 17 | > Note: you should keep the LICENSE file in your project. 18 | 19 | ## How do I use Node's fs in this template? 20 | Please check out [this guide](https://github.com/reZach/secure-electron-template/blob/master/docs/newtoelectron.md). 21 | 22 | ## Do you have a plain JS version of the template? 23 | No, but you can start with this template and follow the steps [outlined here](https://github.com/reZach/secure-electron-template/issues/57#issuecomment-777891491). 24 | 25 | ## Can I use `yarn` to install dependencies? 26 | Yes, but you'll have to follow [a few steps](https://github.com/reZach/secure-electron-template/issues/62) to get it working. 27 | 28 | ## How do I set up my own license keys? 29 | Please refer to [these instructions](https://github.com/reZach/secure-electron-license-keys) first. If you have further questions, you may post a question in the appropriate repo. 30 | 31 | ## Can I use typescript with this template? 32 | Yes, you can! Simply convert any of the files in the app/src directory to a Typescript extension. If you desire to convert some of the Electron-related files to Typescript, I'd suggest you pull inspiration from the [discussion here](https://github.com/reZach/secure-electron-template/issues/47). 33 | 34 | #### Question not answered? 35 | Please [post an issue](https://github.com/reZach/secure-electron-template/issues/new) and we will add to this page with questions that you have! -------------------------------------------------------------------------------- /.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 | 91 | # Bundled files 92 | app/dist/ 93 | 94 | # Packed apps 95 | dist/ 96 | 97 | # VSCode specific 98 | .vscode/ 99 | 100 | # Logfile specific for development builds 101 | dev-scripts/webpack-dev-server.log 102 | dev-scripts/webpack-dev-server-error.log 103 | 104 | # License-specific files 105 | license.data 106 | public.key -------------------------------------------------------------------------------- /docs/scripts.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | This page is specific to the scripts in the package.json file; what they do and why we have them. 3 | 4 | #### Running locally 5 | To run the template locally, run `npm run dev`. 6 | 7 | When this command is run, it will make use of code within the **dev-scripts** folder. [See here](https://github.com/reZach/secure-electron-template/blob/master/docs/architecture.md#dev-scripts) if you'd like additional information. 8 | 9 | #### Running production 10 | You can test your production builds with the `npm run prod` command, this will load your application with electron and the production config of webpack. It is the production build that is used when packaging your application (below). 11 | 12 | #### Running E2E tests 13 | You can run E2E tests with the `npm run test` command. 14 | 15 | #### Packaging your application 16 | You can package up your application using any of the following commands: 17 | ``` 18 | npm run dist-mac 19 | npm run dist-linux 20 | npm run dist-windows 21 | npm run dist-all 22 | ``` 23 | 24 | These commands make use of [electron-builder](https://www.electron.build) to build your app for production. 25 | 26 | #### Generating translation files 27 | Translations for multiple languages can be generated automatically without manual effort. To create translations, run `npm run translate`. 28 | > Note - There are additional details/setup that must be done the first time in `app/electron/localization/translateMissing.js` before running the command successfully. There is also additional information in this file how the translation process works. 29 | 30 | #### Audit your application 31 | Thanks to [`@doyensec/electronegativity`](https://github.com/doyensec/electronegativity), we can audit that our application meets all of the secure practices as recommended by the Electron team. To run it, run `npm run audit-app`. 32 | > Note - there are limitations of AST/DOM parsing (which the package uses) to verify secure practices. Some results of the report are false positives (ie. `LIMIT_NAVIGATION_GLOBAL_CHECK` and `PERMISSION_REQUEST_HANDLER_GLOBAL_CHECK`). -------------------------------------------------------------------------------- /docs/src.md: -------------------------------------------------------------------------------- 1 | # Src 2 | Here's what the template looks like on a fresh install: 3 | ``` 4 | components/ 5 | constants/ 6 | core/ 7 | pages/ 8 | redux/ 9 | index.html 10 | index.jsx 11 | ``` 12 | 13 | #### components 14 | This folder holds reusable react components you may use in your application. This folder is different than the **pages** folder in that a page represents a container that would hold one to many components. 15 | 16 | > Think of a component as something _smaller_ than a page, like a reusable module or the like. 17 | 18 | #### constants 19 | Constant values that your app might need. All this folder has is a dictionary of keys/routes necessary for [react-router](https://github.com/ReactTraining/react-router) to work. If you were to add another page, this would be one file you'd have to modify. 20 | 21 | #### core 22 | Contains the "bones" that sets up redux as well as your routes. You'll also need to modify the routes file in this folder if you add another page in your app. 23 | 24 | #### pages 25 | Contains pages in your application. Think of a page as a distinct screen. If you had a multi-screen app, you'd need many pages. 26 | 27 | #### redux 28 | Contains all redux-specific files, such as slices, reducers and your redux store. 29 | 30 | #### index.html 31 | This file is the _template_ for your application. With some webpack plugins that are setup for the application, this file will be transformed into bundled .html file that your application will render. The bundled .html file lives in `./app/dist/`. 32 | 33 | When building the application for production, this file gets the [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag added it to load resources over a non-file:/// origin because [it is more secure](https://github.com/reZach/secure-electron-template/issues/2). Otherwise, the differences between production and non-production are identical. 34 | 35 | #### index.jsx 36 | The [entry point](https://webpack.js.org/concepts/entry-points/) of your application where webpack generates your application bundle code from. You likely won't need to touch this file at all, but it's important to know what it's there for. -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | This template is laid out in order to maintain a clear separation-of-concerns (SoC) and composability in order for you to take the template in any way you need to build your app. At the root level we have a few folders: 3 | 4 | ``` 5 | app/ 6 | dev-scripts/ 7 | docs/ 8 | resources/ 9 | test/ 10 | ``` 11 | 12 | #### app 13 | Contains everything for your app. All of your js/css files will go here as well as the electron-specific code. You can go [here](https://github.com/reZach/secure-electron-template/blob/master/docs/app.md) to find more information about this directory. 14 | 15 | #### dev-scripts 16 | Due to limitations in running electron _after_ a webpack development server has been started [and successfully compiled], additional scripts that run the development Electron configuration are in this directory that ensure we only start our _development_ Electron configuration _after_ webpack has loaded completely. 17 | 18 | #### docs 19 | Houses documentation pages such as this one. 20 | 21 | #### resources 22 | Any resources your electron app needs in for building/distributing executables should go here - icons are a great example. 23 | 24 | #### test 25 | Contains [mocha](https://mochajs.org/) tests you may use for E2E (end-to-end) testing. 26 | 27 | ## configs 28 | At the root level we also have some configuration files. 29 | 30 | ``` 31 | .babelrc 32 | package.json 33 | webpack configs 34 | ``` 35 | 36 | #### .babelrc 37 | In the babel configuration file we've set up aliases in order for you to import files with a little less typing. More information can [be found here](https://www.npmjs.com/package/babel-plugin-module-resolver). There are also a few babel presets for ES2015 features and react (so that we can handle .jsx files). 38 | 39 | #### package.json 40 | Where all the NPM modules are stored, as well as build and package scripts. If you want more detail on these scripts, [head over here](https://github.com/reZach/secure-electron-template/blob/master/docs/scripts.md). 41 | 42 | #### webpack[.config|.development|.production].js 43 | These files hold the webpack config for the template. The base template, `webpack.config.js` is used for both environments (development and production) while the other two are used for their respective environment. -------------------------------------------------------------------------------- /app/localization/whitelist.js: -------------------------------------------------------------------------------- 1 | // Contains a whitelist of languages for our app 2 | const whitelistMap = { 3 | af: "Afrikaans", //Afrikaans 4 | ar: "عربى", // Arabic 5 | am: "አማርኛ", // Amharic 6 | bg: "български", // Bulgarian 7 | ca: "Català", // Catalan 8 | cs: "čeština", // Czech 9 | da: "Dansk", // Danish 10 | de: "Deutsche", // German 11 | el: "Ελληνικά", // Greek 12 | en: "English", 13 | es: "Español", // Spanish 14 | et: "Eestlane", // Estonian 15 | fa: "فارسی", // Persian 16 | fi: "Suomalainen", // Finnish 17 | fil: "Pilipino", // Filipino 18 | fr: "Français", // French 19 | gu: "ગુજરાતી", // Gujarati 20 | he: "עברית", // Hebrew 21 | hi: "हिंदी", // Hindi 22 | hr: "Hrvatski", // Croatian 23 | hu: "Magyar", // Hungarian 24 | id: "Indonesia", // Indonesian 25 | it: "Italiano", // Italian 26 | ja: "日本語", // Japanese 27 | kn: "ಕನ್ನಡ", // Kannada 28 | ko: "한국어", // Korean 29 | lt: "Lietuvis", // Lithuanian 30 | lv: "Latvietis", // Latvian 31 | ml: "മലയാളം", // Malayalam 32 | mr: "मराठी", // Marathi 33 | ms: "Melayu", // Malay 34 | nl: "Nederlands", // Dutch 35 | no: "norsk", // Norwegian 36 | pl: "Polskie", // Polish 37 | pt: "Português", // Portuguese 38 | ro: "Română", // Romanian 39 | ru: "Pусский", // Russian 40 | sk: "Slovenský", // Slovak 41 | sr: "Српски", // Serbian 42 | sv: "Svenska", // Swedish 43 | sw: "Kiswahili", // Swahili 44 | ta: "தமிழ்", // Tamil 45 | te: "తెలుగు", // Telugu 46 | th: "ไทย", // Thai 47 | tr: "Türk", // Turkish 48 | uk: "Українська", // Ukranian 49 | vi: "Tiếng Việt", // Vietnamese 50 | zh_CN: "简体中文" // Chinese 51 | }; 52 | 53 | const Whitelist = (function() { 54 | const keys = Object.keys(whitelistMap); 55 | const clickFunction = function(channel, lng, i18nextMainBackend) { 56 | return function(menuItem, browserWindow, event) { 57 | 58 | // Solely within the top menu 59 | i18nextMainBackend.changeLanguage(lng); 60 | 61 | // Between renderer > main process 62 | browserWindow.webContents.send(channel, { 63 | lng 64 | }); 65 | }; 66 | }; 67 | 68 | return { 69 | langs: keys, 70 | buildSubmenu: function(channel, i18nextMainBackend) { 71 | let submenu = []; 72 | 73 | for (const key of keys) { 74 | submenu.push({ 75 | label: whitelistMap[key], 76 | click: clickFunction(channel, key, i18nextMainBackend) 77 | }); 78 | } 79 | 80 | return submenu; 81 | } 82 | }; 83 | })(); 84 | 85 | module.exports = Whitelist; 86 | -------------------------------------------------------------------------------- /app/src/pages/motd/motd.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { changeMessage } from "Redux/components/home/homeSlice"; 4 | import { 5 | writeConfigRequest, 6 | useConfigInMainRequest, 7 | } from "secure-electron-store"; 8 | 9 | class Motd extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | 13 | this.state = { 14 | message: "", 15 | }; 16 | 17 | this.onChangeMessage = this.onChangeMessage.bind(this); 18 | this.onSubmitMessage = this.onSubmitMessage.bind(this); 19 | } 20 | 21 | componentDidMount() { 22 | // Request so that the main process can use the store 23 | window.api.store.send(useConfigInMainRequest); 24 | } 25 | 26 | onChangeMessage(event) { 27 | const { value } = event.target; 28 | this.setState((_state) => ({ 29 | message: value, 30 | })); 31 | } 32 | 33 | onSubmitMessage(event) { 34 | event.preventDefault(); // prevent navigation 35 | this.props.changeMessage(this.state.message); // update redux store 36 | window.api.store.send(writeConfigRequest, "motd", this.state.message); // save message to store (persist) 37 | 38 | // reset 39 | this.setState((_state) => ({ 40 | message: "", 41 | })); 42 | } 43 | 44 | render() { 45 | return ( 46 | 47 |
48 |
49 |

{this.props.home.message}

50 |
51 | Your message of the day will persist 52 |
if you close and re-open the app. 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | 65 | 69 |
70 |
71 |
72 |
73 |
74 | ); 75 | } 76 | } 77 | 78 | const mapStateToProps = (state, _props) => ({ 79 | home: state.home, 80 | }); 81 | const mapDispatch = { changeMessage }; 82 | 83 | export default connect(mapStateToProps, mapDispatch)(Motd); 84 | -------------------------------------------------------------------------------- /docs/secureapps.md: -------------------------------------------------------------------------------- 1 | # Building a secure app 2 | What makes an app secure? Generally this means to follow the principle of **least privilege**, that is, to only give you the bare minimum necessary privileges necessary. This means your app should not request administrator access if it doesn't need it, and unnecessary libraries should [not be included if not used](https://martinfowler.com/bliki/Yagni.html). 3 | 4 | Before electron v5, this concept wasn't followed as closely as it probably should have been. Electron apps designed pre-v5 were built like this: 5 | 6 | ![Pre-v5 electron apps](https://github.com/reZach/secure-electron-template/blob/master/docs/imgs/pre-v5.png "Electron apps before version 5") 7 | 8 | The bridge between the renderer and main components were the remote module and nodeIntegration. Node integration is what makes electron so powerful, but also [very vulnerable to hacking](https://snyk.io/vuln/npm:electron). Tightly integrating the node modules in the renderer (or interactible/visible parts of your app) expose you to RCE and XSS, to name a few problems. 9 | 10 | [Beginning with version 5](https://electronjs.org/docs/api/breaking-changes#planned-breaking-api-changes-50), electron by default is turning off these unsafe options and preferring more safe ones by default. The communication that happens between the renderer and main process is decoupled, and more secure: 11 | 12 | ![v5+ electron apps](https://github.com/reZach/secure-electron-template/blob/master/docs/imgs/post-v5.png "Electron apps beginning with version 5") 13 | 14 | IPC (inter-process communication) can be used to exchange messages between processes, and the preload script can extend the capabilites of the renderer process (e.g: inject modules from the main processes). This separation of concern gives us the ability to apply **the principle of least privilege**. 15 | 16 | My personal experience with electron, is that their [release schedule is crazy](https://electronjs.org/docs/tutorial/electron-timelines), with only a few months between each major release. Electron is a relatively young framework, but it's under very active development which makes it hard to keep up with! This quick release cadence is in part in place to keep [bugs fixed sooner than later](https://electronjs.org/docs/tutorial/security#17-use-a-current-version-of-electron). While it's good for security, it can sometimes be tedious for developers to keep up to date with the framework. 17 | 18 | What's even more troubling is many of the frameworks that integrate with electron are still applying these old/insecure (pre v5) patterns. If you wish to be more secure then you might have to rewrite some of them. 19 | 20 | There is a little bit more work required to use IPC, but I'm working on it! Regardless, electron developers have worked hard to enable application developers to write more secure apps! 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 13 * * 3' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['javascript'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /app/electron/protocol.js: -------------------------------------------------------------------------------- 1 | /* 2 | Reasonably Secure Electron 3 | Copyright (C) 2021 Bishop Fox 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | ------------------------------------------------------------------------- 10 | Implementing a custom protocol achieves two goals: 11 | 1) Allows us to use ES6 modules/targets for Angular 12 | 2) Avoids running the app in a file:// origin 13 | */ 14 | 15 | const fs = require("fs"); 16 | const path = require("path"); 17 | 18 | const DIST_PATH = path.join(__dirname, "../../app/dist"); 19 | const scheme = "app"; 20 | 21 | const mimeTypes = { 22 | ".js": "text/javascript", 23 | ".mjs": "text/javascript", 24 | ".html": "text/html", 25 | ".htm": "text/html", 26 | ".json": "application/json", 27 | ".css": "text/css", 28 | ".svg": "image/svg+xml", 29 | ".ico": "image/vnd.microsoft.icon", 30 | ".png": "image/png", 31 | ".jpg": "image/jpeg", 32 | ".map": "text/plain" 33 | }; 34 | 35 | function charset(mimeExt) { 36 | return [".html", ".htm", ".js", ".mjs"].some((m) => m === mimeExt) ? 37 | "utf-8" : 38 | null; 39 | } 40 | 41 | function mime(filename) { 42 | const mimeExt = path.extname(`${filename || ""}`).toLowerCase(); 43 | const mimeType = mimeTypes[mimeExt]; 44 | return mimeType ? { mimeExt, mimeType } : { mimeExt: null, mimeType: null }; 45 | } 46 | 47 | function requestHandler(req, next) { 48 | const reqUrl = new URL(req.url); 49 | let reqPath = path.normalize(reqUrl.pathname); 50 | if (reqPath === "/") { 51 | reqPath = "/index.html"; 52 | } 53 | const reqFilename = path.basename(reqPath); 54 | fs.readFile(path.join(DIST_PATH, reqPath), (err, data) => { 55 | const { mimeExt, mimeType } = mime(reqFilename); 56 | if (!err && mimeType !== null) { 57 | next({ 58 | mimeType, 59 | charset: charset(mimeExt), 60 | data 61 | }); 62 | } else { 63 | console.error(err); 64 | } 65 | }); 66 | } 67 | 68 | module.exports = { 69 | scheme, 70 | requestHandler 71 | }; -------------------------------------------------------------------------------- /dev-scripts/launchDevServer.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { 3 | exec 4 | } = require("child_process"); 5 | const logFilePath = "./dev-scripts/webpack-dev-server.log"; 6 | const errorLogFilePath = "./dev-scripts/webpack-dev-server-error.log"; 7 | const interval = 100; 8 | const showHint = 600 * 3; // show hint after 3 minutes (60 sec * 3) 9 | let hintCounter = 1; 10 | 11 | // Poll webpack-dev-server.log until the webpack bundle has compiled successfully 12 | const intervalId = setInterval(function () { 13 | try { 14 | if (fs.existsSync(logFilePath)) { 15 | const log = fs.readFileSync(logFilePath, { 16 | encoding: "utf8" 17 | }); 18 | 19 | // "compiled successfully" is the string we need to find 20 | // to know that webpack is done bundling everything and we 21 | // can load our Electron app with no issues. We split up the 22 | // validation because the output contains non-standard characters. 23 | const compiled = log.indexOf("compiled"); 24 | if (compiled >= 0 && log.indexOf("successfully", compiled) >= 0) { 25 | console.log("Webpack development server is ready, launching Electron app."); 26 | clearInterval(intervalId); 27 | 28 | // Start our electron app 29 | const electronProcess = exec("cross-env NODE_ENV=development electron ."); 30 | electronProcess.stdout.on("data", function(data) { 31 | process.stdout.write(data); 32 | }); 33 | electronProcess.stderr.on("data", function(data) { 34 | process.stdout.write(data); 35 | }); 36 | } else if (log.indexOf("Module build failed") >= 0) { 37 | 38 | if (fs.existsSync(errorLogFilePath)) { 39 | const errorLog = fs.readFileSync(errorLogFilePath, { 40 | encoding: "utf8" 41 | }); 42 | 43 | console.log(errorLog); 44 | console.log(`Webpack failed to compile; this error has also been logged to '${errorLogFilePath}'.`); 45 | clearInterval(intervalId); 46 | 47 | return process.exit(1); 48 | } else { 49 | console.log("Webpack failed to compile, but the error is unknown.") 50 | clearInterval(intervalId); 51 | 52 | return process.exit(1); 53 | } 54 | } else { 55 | hintCounter++; 56 | 57 | // Show hint so user is not waiting/does not know where to 58 | // look for an error if it has been thrown and/or we are stuck 59 | if (hintCounter > showHint){ 60 | console.error(`Webpack is likely failing for an unknown reason, please check '${errorLogFilePath}' for more details.`); 61 | clearInterval(intervalId); 62 | 63 | return process.exit(1); 64 | } 65 | } 66 | } 67 | } catch (error) { 68 | // Exit with an error code 69 | console.error("Webpack or electron fatal error" + error); 70 | clearInterval(intervalId); 71 | 72 | return process.exit(1); 73 | } 74 | }, interval); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { 2 | CleanWebpackPlugin 3 | } = require("clean-webpack-plugin"); 4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 5 | const webpack = require("webpack"); 6 | const path = require("path"); 7 | 8 | module.exports = { 9 | target: "web", // Our app can run without electron 10 | entry: ["./app/src/index.tsx"], // The entry point of our app; these entry points can be named and we can also have multiple if we'd like to split the webpack bundle into smaller files to improve script loading speed between multiple pages of our app 11 | output: { 12 | path: path.resolve(__dirname, "app/dist"), // Where all the output files get dropped after webpack is done with them 13 | filename: "bundle.js" // The name of the webpack bundle that's generated 14 | }, 15 | resolve: { 16 | fallback: { 17 | "crypto": require.resolve("crypto-browserify"), 18 | "buffer": require.resolve("buffer/"), 19 | "path": require.resolve("path-browserify"), 20 | "stream": require.resolve("stream-browserify") 21 | } 22 | }, 23 | module: { 24 | rules: [{ 25 | // loads .html files 26 | test: /\.(html)$/, 27 | include: [path.resolve(__dirname, "app/src")], 28 | use: { 29 | loader: "html-loader", 30 | options: { 31 | sources: { 32 | "list": [{ 33 | "tag": "img", 34 | "attribute": "data-src", 35 | "type": "src" 36 | }] 37 | } 38 | } 39 | } 40 | }, 41 | // loads .js/jsx/tsx files 42 | { 43 | test: /\.[jt]sx?$/, 44 | include: [path.resolve(__dirname, "app/src")], 45 | loader: "babel-loader", 46 | resolve: { 47 | extensions: [".js", ".jsx", ".ts", ".tsx", ".json"] 48 | } 49 | }, 50 | // loads .css files 51 | { 52 | test: /\.css$/, 53 | include: [ 54 | path.resolve(__dirname, "app/src"), 55 | path.resolve(__dirname, "node_modules/"), 56 | ], 57 | use: [ 58 | MiniCssExtractPlugin.loader, 59 | "css-loader" 60 | ], 61 | resolve: { 62 | extensions: [".css"] 63 | } 64 | }, 65 | // loads common image formats 66 | { 67 | test: /\.(svg|png|jpg|gif)$/, 68 | include: [ 69 | path.resolve(__dirname, "resources/images") 70 | ], 71 | type: "asset/inline" 72 | }, 73 | // loads common font formats 74 | { 75 | test: /\.(eot|woff|woff2|ttf)$/, 76 | include: [ 77 | path.resolve(__dirname, "resources/fonts") 78 | ], 79 | type: "asset/inline" 80 | } 81 | ] 82 | }, 83 | plugins: [ 84 | // fix "process is not defined" error; 85 | // https://stackoverflow.com/a/64553486/1837080 86 | new webpack.ProvidePlugin({ 87 | process: "process/browser.js", 88 | }), 89 | new CleanWebpackPlugin() 90 | ] 91 | }; -------------------------------------------------------------------------------- /docs/sandbox.md: -------------------------------------------------------------------------------- 1 | # Sandbox 2 | 3 | Whenever you're deploying your application, whether in a packaged form or running it from the command line, it's worth verifying that electron renderer is actually running in sandboxed mode. 4 | 5 | This document currently explains how the procedure to check if the sandbox is enabled for the following operating systems: 6 | - Linux (seccomp-bpf) (todo: namespace sandbox) 7 | - OSX 8 | 9 | Not supported: 10 | - Windows 11 | 12 | If you however do know a way of testing those too, then please update this document with the information. 13 | 14 | A good _indication_ that the sanbox _might_ be enabled is that the `--no-sandbox` is nowhere to be found. 15 | 16 | ## linux: verify seccomp-bpf sandbox 17 | 18 | Run the application you want to test. 19 | This can be from the actual source code or even a packaged distributable (.zip, .deb, snap..). 20 | 21 | We need to get a list of the process ids (PIDs for short). 22 | ``` 23 | $ ps aux | grep "electron" 24 | ``` 25 | 26 | Only the renderer processes are supposed to be sandboxed, so grab the PIDs of the processes which have the --renderer flag 27 | ``` 28 | user 22350 0.0 0.0 4340 772 pts/0 S+ 21:50 0:00 sh -c electron --enable-sandbox . 29 | user 22351 1.3 0.4 742836 24072 pts/0 Sl+ 21:50 0:00 node /home/user/projects/electron/electron-sandbox/sandbox-preload-simple/node_modules/.bin/electron --enable-sandbox . 30 | user 22357 8.6 1.7 1147784 91584 pts/0 Sl+ 21:50 0:00 /somepath/electron --enable-sandbox . 31 | user 22360 0.0 0.5 323788 29296 pts/0 S+ 21:50 0:00 /somepath/electron --type=zygote 32 | user 22362 0.0 0.1 323788 8400 pts/0 S+ 21:50 0:00 /somepath/electron --type=zygote 33 | user 22394 2.0 1.2 717784 67312 pts/0 Sl+ 21:50 0:00 /somepath/electron --type=renderer --primordial-pipe-token=61D5BD0CAC441B2B2628002A0299952A --lang=en-US --enable-sandbox --app-path=/home/user/projects/electron/electron-sandbox/sandbox-preload-simple --node-integration=false --webview-tag=false --enable-sandbox --preload=/home/user/projects/electron/electron-sandbox/sandbox-preload-simple/preload-simple.js --context-isolation --enable-pinch --num-raster-threads=4 --enable-main-frame-before-activation --content-image-texture-target=... --renderer-client-id=4 --shared-files=v8_natives_data:100,v8_snapshot_data:101 34 | user 22407 0.0 0.0 12728 2096 pts/1 S+ 21:50 0:00 grep electron 35 | ``` 36 | We make sure that the `--no-sandbox` flag is NOWHERE to be found. If you see a --no-sandbox in a renderer, then it will not be sandboxed. 37 | 38 | We grab the PID of the renderer process, which is `22394` in this particular instance. 39 | 40 | We check if the Seccomp BPF sandbox is running with the following command 41 | 42 | ``` 43 | $ cat /proc/22394/status | grep "Seccomp" 44 | Seccomp: 2 45 | ``` 46 | 47 | If it returns 2 the it means the sandbox is enabled! 48 | ``` 49 | 0 // SECCOMP_MODE_DISABLED 50 | 1 // SECCOMP_MODE_STRICT 51 | 2 // SECCOMP_MODE_FILTER 52 | ``` 53 | 54 | ## MacOS: verify sandbox 55 | * Launch Activity Monitor (available in `/Applications/Utilities`). 56 | * In Activity Monitor, choose View > Columns. 57 | * Ensure that the Sandbox menu item is checked. 58 | * In the Sandbox column, confirm that the value for the Quick Start app is Yes. 59 | * To make it easier to locate the app in Activity monitor, enter the name of the Quick Start app in the Filter field. 60 | 61 | src: https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxQuickStart/AppSandboxQuickStart.html#//apple_ref/doc/uid/TP40011183-CH2-SW3 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secure-electron-template", 3 | "version": "22.0.1", 4 | "description": "The best way to build Electron apps with security in mind.", 5 | "private": true, 6 | "main": "app/electron/main.js", 7 | "scripts": { 8 | "postinstall": "electron-builder install-app-deps", 9 | "audit-app": "npx electronegativity -i ./ -x LimitNavigationGlobalCheck,PermissionRequestHandlerGlobalCheck", 10 | "translate": "node ./app/localization/translateMissing.js", 11 | "dev-server": "cross-env NODE_ENV=development webpack serve --config ./webpack.development.js > dev-scripts/webpack-dev-server.log 2> dev-scripts/webpack-dev-server-error.log", 12 | "dev": "concurrently --success first \"node dev-scripts/prepareDevServer.js\" \"node dev-scripts/launchDevServer.js\" -k", 13 | "prod-build": "cross-env NODE_ENV=production npx webpack --mode=production --config ./webpack.production.js", 14 | "prod": "npm run prod-build && electron .", 15 | "pack": "electron-builder --dir", 16 | "dist": "npm run test && npm run prod-build && electron-builder", 17 | "dist-mac": "npm run test && npm run prod-build && electron-builder --mac", 18 | "dist-linux": "npm run test && npm run prod-build && electron-builder --linux", 19 | "dist-windows": "npm run prod-build && electron-builder --windows", 20 | "dist-all": "npm run test && npm run prod-build && electron-builder --mac --linux --windows", 21 | "test": "mocha" 22 | }, 23 | "build": { 24 | "productName": "YourProductName", 25 | "appId": "com.yourcompany|electron.yourproductname", 26 | "directories": { 27 | "buildResources": "resources" 28 | }, 29 | "files": [ 30 | "app/dist/**/*", 31 | "app/electron/**/*", 32 | "app/localization/!(locales)", 33 | "LICENSE" 34 | ], 35 | "extraFiles": [ 36 | "app/localization/locales/**/*", 37 | "license.data", 38 | "public.key" 39 | ], 40 | "win": { 41 | "target": [ 42 | "nsis", 43 | "msi" 44 | ] 45 | }, 46 | "linux": { 47 | "target": [ 48 | "deb", 49 | "rpm", 50 | "snap", 51 | "AppImage" 52 | ] 53 | } 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/reZach/secure-electron-template.git" 58 | }, 59 | "keywords": [ 60 | "electron", 61 | "security", 62 | "secure", 63 | "template", 64 | "javascript", 65 | "react", 66 | "redux", 67 | "webpack", 68 | "i18n", 69 | "boilerplate" 70 | ], 71 | "author": "reZach", 72 | "license": "MIT", 73 | "bugs": { 74 | "url": "https://github.com/reZach/secure-electron-template/issues" 75 | }, 76 | "homepage": "https://github.com/reZach/secure-electron-template#readme", 77 | "browserslist": [ 78 | "last 2 Chrome versions" 79 | ], 80 | "devDependencies": { 81 | "@babel/core": "^7.18.9", 82 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 83 | "@babel/plugin-transform-react-jsx": "^7.18.6", 84 | "@babel/preset-env": "^7.18.9", 85 | "@babel/preset-react": "^7.18.6", 86 | "@babel/preset-typescript": "^7.18.6", 87 | "@doyensec/electronegativity": "^1.9.1", 88 | "@google-cloud/translate": "^7.0.0", 89 | "@types/react": "^18.0.15", 90 | "@types/react-dom": "^18.0.6", 91 | "babel-loader": "^8.2.5", 92 | "babel-plugin-module-resolver": "^4.1.0", 93 | "buffer": "^6.0.3", 94 | "clean-webpack-plugin": "^4.0.0", 95 | "concurrently": "^7.3.0", 96 | "cross-env": "^7.0.3", 97 | "crypto-browserify": "^3.12.0", 98 | "csp-html-webpack-plugin": "^5.1.0", 99 | "css-loader": "^6.7.1", 100 | "css-minimizer-webpack-plugin": "^4.0.0", 101 | "electron": "^19.0.10", 102 | "electron-builder": "^23.1.0", 103 | "electron-debug": "^3.2.0", 104 | "html-loader": "^4.1.0", 105 | "html-webpack-plugin": "^5.5.0", 106 | "mini-css-extract-plugin": "^2.6.1", 107 | "mocha": "^10.0.0", 108 | "path-browserify": "^1.0.1", 109 | "spectron": "^19.0.0", 110 | "stream-browserify": "^3.0.0", 111 | "typescript": "4.7.4", 112 | "webpack": "^5.74.0", 113 | "webpack-cli": "^4.10.0", 114 | "webpack-dev-server": "^4.9.3", 115 | "webpack-merge": "^5.8.0" 116 | }, 117 | "dependencies": { 118 | "@loadable/component": "^5.15.2", 119 | "@reduxjs/toolkit": "^1.8.3", 120 | "bulma": "^0.9.4", 121 | "easy-redux-undo": "^1.0.5", 122 | "electron-devtools-installer": "^3.2.0", 123 | "i18next": "^21.8.14", 124 | "i18next-electron-fs-backend": "^3.0.0", 125 | "i18next-fs-backend": "^1.1.4", 126 | "lodash": "4.17.21", 127 | "lodash.merge": "^4.6.2", 128 | "process": "^0.11.10", 129 | "react": "^18.2.0", 130 | "react-dom": "^18.2.0", 131 | "react-i18next": "^11.18.3", 132 | "react-redux": "^8.0.2", 133 | "react-router": "^6.3.0", 134 | "react-router-dom": "^6.3.0", 135 | "redux": "^4.2.0", 136 | "redux-first-history": "^5.0.12", 137 | "secure-electron-context-menu": "^1.3.3", 138 | "secure-electron-license-keys": "^1.1.3", 139 | "secure-electron-store": "^4.0.2" 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/localization/translateMissing.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { 3 | readdirSync, 4 | statSync 5 | } = require("fs"); 6 | const { 7 | join 8 | } = require("path") 9 | const { 10 | Translate 11 | } = require("@google-cloud/translate").v2; 12 | 13 | // READ THIS NOTICE 14 | /* 15 | In order to run this file, you must do the following steps: 16 | 17 | 1. Select or create a Cloud Platform project 18 | 2. Enable billing for your project 19 | 3. Enable the Cloud Translation API 20 | 4. Set up authentication with a service account [so you can access the API from your local workstation] 21 | (These steps are found with more details here: https://www.npmjs.com/package/@google-cloud/translate) 22 | 23 | Once this is done, update 'projectId' below with your GCP project id, and remove the return statement below this comment. 24 | 25 | ---- 26 | 27 | BASIC WORKFLOW 28 | 29 | In order to use this file effectively, which is run with the command 'npm run translate', you would 30 | create translated strings like in menu.js or localization.jsx. You would then run the app and change 31 | languages in order that the keys for these translated strings are populated in the various other 32 | languages' missing.json files. Once this is done for all languages you'd like to create translations for, you may run `npm run translate` in order that the missing translation files be translated with 33 | the Google Translate API. 34 | 35 | Note - it is important that 'fromLanguage' be updated to the language that the keys are in the various 36 | translation[.missing].json files. It is this variable that's used by Google to determine the source 37 | language from which to translate. 38 | */ 39 | console.log("The translateMissing.js file must be updated before it can be ran."); 40 | return; 41 | 42 | const projectId = "YOUR_PROJECT_ID"; 43 | 44 | // Instantiates a client 45 | const translate = new Translate({ 46 | projectId 47 | }); 48 | 49 | async function updateTranslations() { 50 | 51 | try { 52 | const root = "./app/localization/locales"; 53 | const fromLanguage = "en"; 54 | 55 | // Get valid languages from Google Translate API 56 | let [googleLanguages] = await translate.getLanguages(); // ie. { code: "en", name: "English" } 57 | googleLanguages = googleLanguages.map(gl => gl.code.replace("-", "_")) 58 | 59 | // Uncomment me to view the various languages Google can translate to/from 60 | //console.log(googleLanguages); 61 | 62 | // Get all language directories; 63 | // https://stackoverflow.com/a/35759360/1837080 64 | const getDirectories = p => readdirSync(p).filter(f => statSync(join(p, f)).isDirectory()); 65 | const languageDirectories = getDirectories(root).filter(d => googleLanguages.includes(d)); 66 | 67 | // For each language, read in any missing translations 68 | // and translate 69 | for (const languageDirectory of languageDirectories) { 70 | 71 | // Check to make sure each language has the proper files 72 | try { 73 | const languageRoot = `${root}/${languageDirectory}`; 74 | const translationFile = `${languageRoot}/translation.json`; 75 | const missingTranslationFile = `${languageRoot}/translation.missing.json`; 76 | 77 | const translationExists = fs.existsSync(translationFile); 78 | const translationMissingExists = fs.existsSync(missingTranslationFile); 79 | 80 | if (translationExists && translationMissingExists) { 81 | 82 | // Read in contents of files 83 | const translations = JSON.parse(fs.readFileSync(translationFile, { 84 | encoding: "utf8" 85 | })); 86 | const missing = JSON.parse(fs.readFileSync(missingTranslationFile, { 87 | encoding: "utf8" 88 | })); 89 | 90 | // Only translate files with actual values 91 | const missingKeys = Object.keys(missing); 92 | if (missingKeys.length > 0){ 93 | 94 | // Translate each of the missing keys to the target language 95 | for (const missingKey of missingKeys){ 96 | const googleTranslation = await translate.translate(missingKey, { 97 | from: fromLanguage, 98 | to: languageDirectory 99 | }); 100 | 101 | // Only set if a value is returned 102 | if (googleTranslation.length > 0){ 103 | translations[missingKey] = googleTranslation[0]; 104 | } 105 | } 106 | 107 | // Write output back to file 108 | fs.writeFileSync(translationFile, JSON.stringify(translations, null, 2)); 109 | fs.writeFileSync(missingTranslationFile, JSON.stringify({}, null, 2)); 110 | 111 | console.log(`Successfully updated translations for ${languageDirectory}`); 112 | } else { 113 | console.log(`Skipped creating translations for ${languageDirectory}; none found!`); 114 | } 115 | } else { 116 | 117 | // Log if we failed 118 | if (!translationExists) { 119 | console.error(`Could not generate translations for language '${languageDirectory}' because ${translationFile} does not exist, skipping!`); 120 | } else if (!translationMissingExists) { 121 | console.error(`Could not generate translations for language '${languageDirectory}' because ${missingTranslationFile} does not exist, skipping!`); 122 | } 123 | } 124 | } catch (error) { 125 | console.error("Failed due to fatal error"); 126 | console.error(error); 127 | } 128 | } 129 | } catch (e) { 130 | console.error("Failed due to fatal error"); 131 | console.error(e); 132 | } 133 | } 134 | 135 | updateTranslations(); 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # secure-electron-template 2 | A current electron app template with the most popular frameworks, designed and built with security in mind. (If you are curious about what makes an electron app secure, please check out [this page](https://github.com/reZach/secure-electron-template/blob/master/docs/secureapps.md)). 3 | 4 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=reZach_secure-electron-template&metric=alert_status)](https://sonarcloud.io/dashboard?id=reZach_secure-electron-template) 5 | [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=reZach_secure-electron-template&metric=security_rating)](https://sonarcloud.io/dashboard?id=reZach_secure-electron-template) 6 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=reZach_secure-electron-template&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=reZach_secure-electron-template) 7 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=reZach_secure-electron-template&metric=bugs)](https://sonarcloud.io/dashboard?id=reZach_secure-electron-template) 8 | [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=reZach_secure-electron-template&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=reZach_secure-electron-template) 9 | 10 | ## How to get started 11 | To get started, clone the repository by clicking the [![Use this template](https://github.com/reZach/secure-electron-template/blob/master/docs/imgs/usethistemplate.png "Use this template")](https://github.com/reZach/secure-electron-template/generate) button, or through the command line (`git clone https://github.com/reZach/secure-electron-template.git`). 12 | 13 | Once cloned, install the dependencies for the repo by running the following commands (you do _not_ have to run the first command if your command line is already inside the newly cloned repository): 14 | 15 | ``` 16 | cd secure-electron-template 17 | npm i 18 | npm run dev 19 | ``` 20 | 21 | > Are you using `yarn`? You'll want to [read this issue](https://github.com/reZach/secure-electron-template/issues/62). 22 | 23 | When you'd like to test your app in production, or package it for distribution, please navigate to [this page](https://github.com/reZach/secure-electron-template/blob/master/docs/scripts.md) for more details on how to do this. 24 | 25 | ## Demo 26 | ![Demo](https://github.com/reZach/secure-electron-template/blob/master/docs/imgs/intro.gif "Demo") 27 | 28 | ## Features 29 | Taken from the [best-practices](https://electronjs.org/docs/tutorial/security) official page, here is what this repository offers! 30 | 31 | 1. [Only load secure content](https://electronjs.org/docs/tutorial/security#1-only-load-secure-content) - ✅ (But the developer is responsible for loading secure assets only 🙂) 32 | 2. [Do not enable node.js integration for remote content](https://electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content) - ✅ 33 | 3. [Enable context isolation for remote content](https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content) - ✅ 34 | 4. [Handle session permission requests from remote content](https://electronjs.org/docs/tutorial/security#4-handle-session-permission-requests-from-remote-content) - ✅ 35 | 5. [Do not disable websecurity](https://electronjs.org/docs/tutorial/security#5-do-not-disable-websecurity) - ✅ 36 | 6. [Define a content security policy](https://electronjs.org/docs/tutorial/security#6-define-a-content-security-policy) - ✅ 37 | 7. [Do not set allowRunningInsecureContent to true](https://electronjs.org/docs/tutorial/security#7-do-not-set-allowrunninginsecurecontent-to-true) - ✅ 38 | 8. [Do not enable experimental features](https://electronjs.org/docs/tutorial/security#8-do-not-enable-experimental-features) - ✅ 39 | 9. [Do not use enableBlinkFeatures](https://electronjs.org/docs/tutorial/security#9-do-not-use-enableblinkfeatures) - ✅ 40 | 10. [Do not use allowpopups](https://electronjs.org/docs/tutorial/security#10-do-not-use-allowpopups) - ✅ 41 | 11. [<webview> verify options and params](https://electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation) - ✅ 42 | 12. [Disable or limit navigation](https://electronjs.org/docs/tutorial/security#12-disable-or-limit-navigation) - ✅ 43 | 13. [Disable or limit creation of new windows](https://electronjs.org/docs/tutorial/security#13-disable-or-limit-creation-of-new-windows) - ✅ 44 | 14. [Do not use openExternal with untrusted content](https://electronjs.org/docs/tutorial/security#14-do-not-use-openexternal-with-untrusted-content) - ✅ 45 | 15. [Disable remote module](https://electronjs.org/docs/tutorial/security#15-disable-the-remote-module) - ✅ 46 | 16. [Filter the remote module](https://electronjs.org/docs/tutorial/security#16-filter-the-remote-module) - ✅ 47 | 17. [Use a current version of electron](https://electronjs.org/docs/tutorial/security#17-use-a-current-version-of-electron) - ✅ 48 | 49 | ## Included frameworks 50 | Built-in to this template are a number of popular frameworks already wired up to get you on the road running. 51 | 52 | - [Electron](https://electronjs.org/) 53 | - [React](https://reactjs.org/) 54 | - [Typescript](https://www.typescriptlang.org) 55 | - [Redux](https://redux.js.org/) (with [Redux toolkit](https://redux-toolkit.js.org/)) 56 | - [Babel](https://babeljs.io/) 57 | - [Webpack](https://webpack.js.org/) (with [webpack-dev-server](https://github.com/webpack/webpack-dev-server)) 58 | - [Electron builder](https://www.electron.build/) (for packaging up your app) 59 | - [Mocha](https://mochajs.org/) 60 | 61 | ## Bonus modules 62 | What would a template be without some helpful additions? 63 | 64 | - [i18next](https://www.i18next.com/) (with [this plugin](https://github.com/reZach/i18next-electron-fs-backend) for localization). 65 | - [Store](https://github.com/reZach/secure-electron-store) (for saving config/data) 66 | - [Context menu](https://github.com/reZach/secure-electron-context-menu) (supports custom context menus) 67 | - [Easy redux undo](https://github.com/reZach/easy-redux-undo) (for undo/redoing your redux actions) 68 | - [License key validation](https://github.com/reZach/secure-electron-license-keys) (for validating a user has the proper license to use your app) **new!** 69 | 70 | ## Architecture 71 | For a more detailed view of the architecture of the template, please check out [here](https://github.com/reZach/secure-electron-template/blob/master/docs/architecture.md). I would _highly_ recommend reading this document to get yourself familiarized with this template. 72 | 73 | ## FAQ 74 | Please see [our faq](https://github.com/reZach/secure-electron-template/blob/master/docs/faq.md) for any common questions you might have. 75 | **NEW TO ELECTRON?** Please visit [this page](https://github.com/reZach/secure-electron-template/blob/master/docs/newtoelectron.md). 76 | 77 | ## Show us your apps! 78 | If you've built any applications with our template, we'd [love to see them!](https://github.com/reZach/secure-electron-template/blob/master/docs/yourapps.md). 79 | -------------------------------------------------------------------------------- /app/src/pages/undoredo/undoredo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { UNDO, REDO, CLEAR, GROUPBEGIN, GROUPEND } from "easy-redux-undo"; 4 | import { increment, decrement } from "Redux/components/counter/counterSlice"; 5 | import { add, remove } from "Redux/components/complex/complexSlice"; 6 | import "./undoredo.css"; 7 | 8 | class UndoRedo extends React.Component { 9 | constructor() { 10 | super(); 11 | 12 | // Counter-specific 13 | this.inc = this.inc.bind(this); 14 | this.dec = this.dec.bind(this); 15 | 16 | // Complex-specific 17 | this.add = this.add.bind(this); 18 | this.remove = this.remove.bind(this); 19 | 20 | // Undo-specific 21 | this.undo = this.undo.bind(this); 22 | this.redo = this.redo.bind(this); 23 | this.undo2 = this.undo2.bind(this); 24 | this.redo2 = this.redo2.bind(this); 25 | this.clear = this.clear.bind(this); 26 | this.groupbegin = this.groupbegin.bind(this); 27 | this.groupend = this.groupend.bind(this); 28 | } 29 | 30 | inc(_event) { 31 | this.props.increment(); 32 | } 33 | 34 | dec(_event) { 35 | this.props.decrement(); 36 | } 37 | 38 | add(_event) { 39 | this.props.add(); 40 | } 41 | 42 | remove(_event) { 43 | this.props.remove(); 44 | } 45 | 46 | undo(_event) { 47 | this.props.UNDO(); 48 | } 49 | 50 | redo(_event) { 51 | this.props.REDO(); 52 | } 53 | 54 | undo2(_event) { 55 | this.props.UNDO(2); 56 | } 57 | 58 | redo2(_event) { 59 | this.props.REDO(2); 60 | } 61 | 62 | clear(_event) { 63 | this.props.CLEAR(); 64 | } 65 | 66 | groupbegin(_event) { 67 | this.props.GROUPBEGIN(); 68 | } 69 | 70 | groupend(_event) { 71 | this.props.GROUPEND(); 72 | } 73 | 74 | render() { 75 | return ( 76 | 77 |
78 |
79 |

Undo/Redo

80 |
81 | Try out modifying, and then undo/redoing the redux history below! 82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 92 |
93 |
94 |
95 |
96 | 101 |
102 | 105 | 108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | 116 |
117 |
118 |
119 |
120 | 125 |
126 | 129 | 134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | 142 |
143 |
144 |
145 |
146 |
147 | 150 | 153 | 156 | 159 | 162 | 167 | 170 |
171 |
172 |
173 |
174 |
175 |
176 |                   {JSON.stringify(this.props.present, null, 2)}
177 |                 
178 |
179 | Undo/Redo state information
180 | Past length: {this.props.past.length} 181 |
182 | Future length: {this.props.future.length} 183 |
184 |
185 |
186 |
187 | ); 188 | } 189 | } 190 | 191 | const mapStateToProps = (state, _props) => ({ 192 | counter: state.undoable.present.counter, 193 | complex: state.undoable.present.complex, 194 | past: state.undoable.past, 195 | present: state.undoable.present, 196 | future: state.undoable.future, 197 | }); 198 | const mapDispatch = { 199 | increment, 200 | decrement, 201 | add, 202 | remove, 203 | UNDO, 204 | REDO, 205 | CLEAR, 206 | GROUPBEGIN, 207 | GROUPEND, 208 | }; 209 | 210 | export default connect(mapStateToProps, mapDispatch)(UndoRedo); 211 | -------------------------------------------------------------------------------- /app/electron/menu.js: -------------------------------------------------------------------------------- 1 | const { Menu, MenuItem, BrowserWindow } = require("electron"); 2 | const i18nBackend = require("i18next-electron-fs-backend"); 3 | const whitelist = require("../localization/whitelist"); 4 | const isMac = process.platform === "darwin"; 5 | 6 | const MenuBuilder = function(mainWindow, appName) { 7 | 8 | // https://electronjs.org/docs/api/menu#main-process 9 | const defaultTemplate = function(i18nextMainBackend) { 10 | return [ 11 | // { role: "appMenu" } 12 | ...(isMac 13 | ? [ 14 | { 15 | label: appName, 16 | submenu: [ 17 | { 18 | role: "about", 19 | label: i18nextMainBackend.t("About") 20 | }, 21 | { 22 | type: "separator" 23 | }, 24 | { 25 | role: "services", 26 | label: i18nextMainBackend.t("Services") 27 | }, 28 | { 29 | type: "separator" 30 | }, 31 | { 32 | role: "hide", 33 | label: i18nextMainBackend.t("Hide") 34 | }, 35 | { 36 | role: "hideothers", 37 | label: i18nextMainBackend.t("Hide Others") 38 | }, 39 | { 40 | role: "unhide", 41 | label: i18nextMainBackend.t("Unhide") 42 | }, 43 | { 44 | type: "separator" 45 | }, 46 | { 47 | role: "quit", 48 | label: i18nextMainBackend.t("Quit") 49 | } 50 | ] 51 | } 52 | ] 53 | : []), 54 | // { role: "fileMenu" } 55 | { 56 | label: i18nextMainBackend.t("File"), 57 | submenu: [ 58 | isMac 59 | ? { 60 | role: "close", 61 | label: i18nextMainBackend.t("Quit") 62 | } 63 | : { 64 | role: "quit", 65 | label: i18nextMainBackend.t("Exit") 66 | } 67 | ] 68 | }, 69 | // { role: "editMenu" } 70 | { 71 | label: i18nextMainBackend.t("Edit"), 72 | submenu: [ 73 | { 74 | role: "undo", 75 | label: i18nextMainBackend.t("Undo") 76 | }, 77 | { 78 | role: "redo", 79 | label: i18nextMainBackend.t("Redo") 80 | }, 81 | { 82 | type: "separator" 83 | }, 84 | { 85 | role: "cut", 86 | label: i18nextMainBackend.t("Cut") 87 | }, 88 | { 89 | role: "copy", 90 | label: i18nextMainBackend.t("Copy") 91 | }, 92 | { 93 | role: "paste", 94 | label: i18nextMainBackend.t("Paste") 95 | }, 96 | ...(isMac 97 | ? [ 98 | { 99 | role: "pasteAndMatchStyle", 100 | label: i18nextMainBackend.t("Paste and Match Style") 101 | }, 102 | { 103 | role: "delete", 104 | label: i18nextMainBackend.t("Delete") 105 | }, 106 | { 107 | role: "selectAll", 108 | label: i18nextMainBackend.t("Select All") 109 | }, 110 | { 111 | type: "separator" 112 | }, 113 | { 114 | label: i18nextMainBackend.t("Speech"), 115 | submenu: [ 116 | { 117 | role: "startspeaking", 118 | label: i18nextMainBackend.t("Start Speaking") 119 | }, 120 | { 121 | role: "stopspeaking", 122 | label: i18nextMainBackend.t("Stop Speaking") 123 | } 124 | ] 125 | } 126 | ] 127 | : [ 128 | { 129 | role: "delete", 130 | label: i18nextMainBackend.t("Delete") 131 | }, 132 | { 133 | type: "separator" 134 | }, 135 | { 136 | role: "selectAll", 137 | label: i18nextMainBackend.t("Select All") 138 | } 139 | ]) 140 | ] 141 | }, 142 | // { role: "viewMenu" } 143 | { 144 | label: i18nextMainBackend.t("View"), 145 | submenu: [ 146 | { 147 | role: "reload", 148 | label: i18nextMainBackend.t("Reload") 149 | }, 150 | { 151 | role: "forcereload", 152 | label: i18nextMainBackend.t("Force Reload") 153 | }, 154 | { 155 | role: "toggledevtools", 156 | label: i18nextMainBackend.t("Toggle Developer Tools") 157 | }, 158 | { 159 | type: "separator" 160 | }, 161 | { 162 | role: "resetzoom", 163 | label: i18nextMainBackend.t("Reset Zoom") 164 | }, 165 | { 166 | role: "zoomin", 167 | label: i18nextMainBackend.t("Zoom In") 168 | }, 169 | { 170 | role: "zoomout", 171 | label: i18nextMainBackend.t("Zoom Out") 172 | }, 173 | { 174 | type: "separator" 175 | }, 176 | { 177 | role: "togglefullscreen", 178 | label: i18nextMainBackend.t("Toggle Fullscreen") 179 | } 180 | ] 181 | }, 182 | // language menu 183 | { 184 | label: i18nextMainBackend.t("Language"), 185 | submenu: whitelist.buildSubmenu(i18nBackend.changeLanguageRequest, i18nextMainBackend) 186 | }, 187 | // { role: "windowMenu" } 188 | { 189 | label: i18nextMainBackend.t("Window"), 190 | submenu: [ 191 | { 192 | role: "minimize", 193 | label: i18nextMainBackend.t("Minimize") 194 | }, 195 | { 196 | role: "zoom", 197 | label: i18nextMainBackend.t("Zoom") 198 | }, 199 | ...(isMac 200 | ? [ 201 | { 202 | type: "separator" 203 | }, 204 | { 205 | role: "front", 206 | label: i18nextMainBackend.t("Front") 207 | }, 208 | { 209 | type: "separator" 210 | }, 211 | { 212 | role: "window", 213 | label: i18nextMainBackend.t("Window") 214 | } 215 | ] 216 | : [ 217 | { 218 | role: "close", 219 | label: i18nextMainBackend.t("Close") 220 | } 221 | ]) 222 | ] 223 | }, 224 | { 225 | role: "help", 226 | label: i18nextMainBackend.t("Help"), 227 | submenu: [ 228 | { 229 | label: i18nextMainBackend.t("Learn More"), 230 | click: async () => { 231 | const { shell } = require("electron"); 232 | await shell.openExternal("https://electronjs.org"); 233 | } 234 | } 235 | ] 236 | } 237 | ]; 238 | }; 239 | 240 | return { 241 | buildMenu: function(i18nextMainBackend) { 242 | const menu = Menu.buildFromTemplate(defaultTemplate(i18nextMainBackend)); 243 | Menu.setApplicationMenu(menu); 244 | 245 | return menu; 246 | } 247 | }; 248 | }; 249 | 250 | module.exports = MenuBuilder; 251 | -------------------------------------------------------------------------------- /app/src/core/nav.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ROUTES from "Constants/routes"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { 5 | validateLicenseRequest, 6 | validateLicenseResponse, 7 | } from "secure-electron-license-keys"; 8 | 9 | class Nav extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | 13 | this.state = { 14 | mobileMenuActive: false, 15 | licenseModalActive: false, 16 | 17 | // license-specific 18 | licenseValid: false, 19 | allowedMajorVersions: "", 20 | allowedMinorVersions: "", 21 | appVersion: "", 22 | licenseExpiry: "", 23 | }; 24 | 25 | this.toggleMenu = this.toggleMenu.bind(this); 26 | this.toggleLicenseModal = this.toggleLicenseModal.bind(this); 27 | this.navigate = this.navigate.bind(this); 28 | } 29 | 30 | componentWillUnmount() { 31 | window.api.licenseKeys.clearRendererBindings(); 32 | } 33 | 34 | componentDidMount() { 35 | // Set up binding to listen when the license key is 36 | // validated by the main process 37 | const _ = this; 38 | 39 | window.api.licenseKeys.onReceive(validateLicenseResponse, function (data) { 40 | // If the license key/data is valid 41 | if (data.success) { 42 | // Here you would compare data.appVersion to 43 | // data.major, data.minor and data.patch to 44 | // ensure that the user's version of the app 45 | // matches their license 46 | _.setState({ 47 | licenseValid: true, 48 | allowedMajorVersions: data.major, 49 | allowedMinorVersions: data.minor, 50 | allowedPatchVersions: data.patch, 51 | appVersion: data.appVersion, 52 | licenseExpiry: data.expire, 53 | }); 54 | } else { 55 | _.setState({ 56 | licenseValid: false, 57 | }); 58 | } 59 | }); 60 | } 61 | 62 | toggleMenu(_event) { 63 | this.setState({ 64 | mobileMenuActive: !this.state.mobileMenuActive, 65 | }); 66 | } 67 | 68 | toggleLicenseModal(_event) { 69 | const previous = this.state.licenseModalActive; 70 | 71 | // Only send license request if the modal 72 | // is not already open 73 | if (!previous) { 74 | window.api.licenseKeys.send(validateLicenseRequest); 75 | } 76 | 77 | this.setState({ 78 | licenseModalActive: !this.state.licenseModalActive, 79 | }); 80 | } 81 | 82 | // Using a custom method to navigate because we 83 | // need to close the mobile menu if we navigate to 84 | // another page 85 | navigate(url) { 86 | this.setState( 87 | { 88 | mobileMenuActive: false, 89 | }, 90 | function () { 91 | this.props.navigate(url); 92 | } 93 | ); 94 | } 95 | 96 | renderLicenseModal() { 97 | return ( 98 |
100 |
101 |
102 | {this.state.licenseValid ? ( 103 |
104 | The license key for this product has been validated and the 105 | following versions of this app are allowed for your use: 106 |
107 | Major versions:{" "} 108 | {this.state.allowedMajorVersions}
109 | Minor versions:{" "} 110 | {this.state.allowedMinorVersions}
111 | Patch versions:{" "} 112 | {this.state.allowedPatchVersions}
113 | Expires on:{" "} 114 | {!this.state.licenseExpiry 115 | ? "never!" 116 | : this.state.licenseExpiry}{" "} 117 |
( 118 | 119 | App version: 120 | {` v${this.state.appVersion.major}.${this.state.appVersion.minor}.${this.state.appVersion.patch}`} 121 | 122 | ) 123 |
124 |
125 |
126 | ) : ( 127 |
128 |
The license key is not valid.
129 |
130 | If you'd like to create a license key, follow these steps: 131 |
    132 |
  1. 133 | Install this package globally ( 134 | npm i secure-electron-license-keys-cli -g). 135 |
  2. 136 |
  3. 137 | Run secure-electron-license-keys-cli. 138 |
  4. 139 |
  5. 140 | Copy public.key and{" "} 141 | license.data into the root folder 142 | of this app. 143 |
  6. 144 |
  7. 145 | Re-run this app (ie. npm run dev). 146 |
  8. 147 |
  9. 148 | If you'd like to further customize your license keys, copy 149 | this link into your browser:{" "} 150 | 151 | https://github.com/reZach/secure-electron-license-keys-cli 152 | 153 | . 154 |
  10. 155 |
156 |
157 |
158 | )} 159 |
160 | 164 |
165 | ); 166 | } 167 | 168 | render() { 169 | return ( 170 | 262 | ); 263 | } 264 | } 265 | 266 | function WithNavigate(props){ 267 | const navigate = useNavigate(); 268 | return