├── .electron-builder.config.js ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .lintstagedrc ├── README.md ├── assets └── icon.png ├── demo.png ├── en-US.js ├── fixtures ├── asar_dir │ └── data.json ├── data │ ├── asar1.json │ ├── asar2.json │ └── package1.json ├── demo.asar ├── demo.asar.zip └── demo.dmg ├── gulpfile.js ├── i18n.config.js ├── package.json ├── scripts ├── build-asar.js ├── build-dmg.js ├── clean-db.sh ├── develop.js ├── electron.js └── launcher.js ├── src ├── app.ts ├── electrom.ts ├── local-storage │ ├── index.ts │ ├── renderer │ │ ├── indexeddb-helper.js │ │ ├── loading.html │ │ ├── main.css │ │ ├── main.html │ │ ├── main.js │ │ └── preload.js │ ├── sqlite.ts │ └── sqlite │ │ ├── test-main.db │ │ ├── test-renderer.db │ │ ├── test-sql.js-worker.db │ │ └── test-sql.js.db ├── main.ts ├── multi-windows │ ├── index.ts │ └── renderer │ │ ├── loading.html │ │ ├── preload.js │ │ ├── window.html │ │ └── window.js ├── network-interface │ └── index.ts ├── preload.ts ├── renderer │ ├── loading.html │ ├── main.html │ ├── main.js │ └── preload.js ├── types.d.ts ├── updator │ ├── index.ts │ └── renderer │ │ ├── loading.html │ │ ├── main.html │ │ ├── main.js │ │ └── preload.js ├── webview-schedule │ ├── index.ts │ └── renderer │ │ ├── loading.html │ │ ├── main.html │ │ ├── main.js │ │ ├── preload.js │ │ ├── webview-preload.js │ │ ├── webview.html │ │ └── webview.js ├── window-manager.ts ├── windows-titlebar │ ├── index.ts │ └── renderer │ │ ├── loading.html │ │ ├── main.html │ │ └── preload.js └── windows-verify-trust │ └── index.ts └── tsconfig.json /.electron-builder.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = async function () { 4 | const config = { 5 | appId: 'electron.modules.sample', 6 | copyright: 'ElectronModules', 7 | productName: 'ElectronModules', 8 | forceCodeSigning: false, 9 | asar: true, 10 | directories: { 11 | output: 'dist/' 12 | }, 13 | linux: { 14 | target: 'deb', 15 | }, 16 | nsis: { 17 | differentialPackage: false, 18 | publish: [ 19 | { 20 | provider: 'github', 21 | }, 22 | ], 23 | }, 24 | mac: { 25 | target: [ 26 | { 27 | target: 'dmg', 28 | arch: ['x64', 'arm64'], 29 | }, 30 | ], 31 | publish: [ 32 | { 33 | provider: 'github', 34 | }, 35 | ], 36 | icon: './assets/icon.png', 37 | }, 38 | }; 39 | return config; 40 | }; 41 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | en-US.js 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('@applint/spec'); 2 | 3 | module.exports = getESLintConfig('react-ts', { 4 | rules: { 5 | 'id-length': 0, 6 | }, 7 | parserOptions: { 8 | ecmaVersion: 2020, 9 | sourceType: 'module', 10 | project: './tsconfig.json', 11 | tsconfigRootDir: __dirname, 12 | createDefaultProgram: true, 13 | }, 14 | settings: { 15 | 'import/resolver': { 16 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 17 | node: {}, 18 | typescript: {}, 19 | }, 20 | react: { 21 | version: 'detect', // React version. "detect" automatically picks the version you have installed. 22 | }, 23 | 'import/parsers': { 24 | '@typescript-eslint/parser': ['.ts', '.tsx'], 25 | }, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | push: 7 | branches: 8 | - '**' 9 | paths-ignore: 10 | - '**.md' 11 | 12 | jobs: 13 | Runner: 14 | timeout-minutes: 10 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ ubuntu-latest ] 20 | node-version: [ 16 ] 21 | steps: 22 | - name: Checkout Git Source 23 | uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | 32 | - name: Install dependencies 33 | run: npm install --force 34 | 35 | - name: Continuous integration 36 | run: npm run lint -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '.github/**' 9 | - '!.github/workflows/ci.yml' 10 | - '!.github/workflows/release.yml' 11 | - '**.md' 12 | - .gitignore 13 | 14 | concurrency: 15 | group: release-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | defaults: 19 | run: 20 | shell: 'bash' 21 | 22 | jobs: 23 | draft_release: 24 | 25 | permissions: 26 | contents: write # Allows this job to create releases 27 | 28 | strategy: 29 | fail-fast: true 30 | matrix: 31 | # os: [ macos-latest, ubuntu-latest, windows-latest ] 32 | os: [ macos-latest, windows-latest ] 33 | # os: [ macos-latest ] 34 | 35 | runs-on: ${{ matrix.os }} 36 | 37 | steps: 38 | - name: Checkout Git Source 39 | uses: actions/checkout@v3 40 | with: 41 | fetch-depth: 0 42 | 43 | - name: Setup Node.js 44 | uses: actions/setup-node@v3 45 | 46 | - name: Install dependencies 47 | run: npm install --force 48 | 49 | - name: Typescript Compile 50 | continue-on-error: true 51 | run: npm run tsc 52 | 53 | - name: Compile artifacts and upload them to github release 54 | uses: nick-fields/retry@v2 55 | with: 56 | timeout_minutes: 15 57 | max_attempts: 3 58 | retry_wait_seconds: 15 59 | retry_on: error 60 | shell: 'bash' 61 | command: npm run build:main -- --publish always 62 | env: 63 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | coverage 4 | fixtures/demo.app 5 | *.gz 6 | *.sw* 7 | *.un~ 8 | .electrom 9 | dist 10 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.(js|jsx|ts|tsx)": ["eslint --fix"] 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-modules-sample 2 | 3 | --- 4 | 5 | [![CI](https://github.com/electron-modules/electron-modules-sample/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/electron-modules/electron-modules-sample/actions/workflows/ci.yml) 6 | [![Release](https://github.com/electron-modules/electron-modules-sample/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/electron-modules/electron-modules-sample/actions/workflows/release.yml) 7 | [![Download](https://img.shields.io/badge/Download-passing-brightgreen?logo=github)](https://github.com/electron-modules/electron-modules-sample/releases) 8 | 9 | The All-In-One sample for https://github.com/electron-modules 10 | 11 |

12 | Macaca 17 |

18 | 19 | ## Develop 20 | 21 | ```bash 22 | $ npm run dev 23 | ``` 24 | 25 | ## Download Usage 26 | 27 | ```bash 28 | $ sudo spctl --master-disable 29 | $ sudo xattr -rd com.apple.quarantine /Applications/ElectronModules.app 30 | ``` 31 | 32 | 33 | 34 | ## Contributors 35 | 36 | |[
xudafeng](https://github.com/xudafeng)
|[
vagusX](https://github.com/vagusX)
|[
snapre](https://github.com/snapre)
| 37 | | :---: | :---: | :---: | 38 | 39 | 40 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Mon Apr 24 2023 17:49:15 GMT+0800`. 41 | 42 | 43 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-modules/electron-modules-sample/e328fccb752bc8d3f3ab4b6b6c1d9c3c5cbd0e24/assets/icon.png -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-modules/electron-modules-sample/e328fccb752bc8d3f3ab4b6b6c1d9c3c5cbd0e24/demo.png -------------------------------------------------------------------------------- /en-US.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "启动失败": "failed", 3 | "系统提示": "System infomation" 4 | }; 5 | -------------------------------------------------------------------------------- /fixtures/asar_dir/data.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-modules/electron-modules-sample/e328fccb752bc8d3f3ab4b6b6c1d9c3c5cbd0e24/fixtures/asar_dir/data.json -------------------------------------------------------------------------------- /fixtures/data/asar1.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "project_version": 1000, 4 | "files": [{ 5 | "url": "http://localhost:8888/fixtures/demo.asar.zip" 6 | }], 7 | "updateType": "asar" 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/data/asar2.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "project_version": 1, 4 | "files": [{ 5 | "url": "http://localhost:8888/fixtures/demo.asar.zip" 6 | }], 7 | "updateType": "asar" 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/data/package1.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.2", 3 | "project_version": 2, 4 | "files": [{ 5 | "url": "http://localhost:8888/fixtures/demo.asar.zip" 6 | }], 7 | "updateType": "package" 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/demo.asar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-modules/electron-modules-sample/e328fccb752bc8d3f3ab4b6b6c1d9c3c5cbd0e24/fixtures/demo.asar -------------------------------------------------------------------------------- /fixtures/demo.asar.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-modules/electron-modules-sample/e328fccb752bc8d3f3ab4b6b6c1d9c3c5cbd0e24/fixtures/demo.asar.zip -------------------------------------------------------------------------------- /fixtures/demo.dmg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-modules/electron-modules-sample/e328fccb752bc8d3f3ab4b6b6c1d9c3c5cbd0e24/fixtures/demo.dmg -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { task } = require('gulp'); 4 | 5 | const develop = require('./scripts/develop'); 6 | 7 | task('dev-app', develop.devApp); 8 | -------------------------------------------------------------------------------- /i18n.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | srcDirs: [ 5 | 'src/**/*.*', 6 | ], 7 | distDir: __dirname, 8 | tokenName: '__i18n', 9 | debug: true, 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-modules-sample", 3 | "version": "0.1.5", 4 | "private": true, 5 | "description": "electron modules sample", 6 | "keywords": [ 7 | "electron" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/electron-modules/electron-modules-sample.git" 12 | }, 13 | "main": "./src/main.js", 14 | "dependencies": { 15 | "@electron/remote": "2", 16 | "debug": "^4.3.4", 17 | "detect-port": "1", 18 | "easy-i18n-cli": "1", 19 | "electrom": "19", 20 | "electron-json-storage-alt": "18", 21 | "electron-webview-schedule": "18", 22 | "electron-windows": "18", 23 | "electron-windows-titlebar": "1", 24 | "graceful-updater": "1", 25 | "lodash": "4", 26 | "lovefield": "^2.1.12", 27 | "moment": "^2.29.4", 28 | "network-interface": "18", 29 | "semver": "^7.3.8", 30 | "sql.js": "^1.8.0", 31 | "windows-verify-trust": "1" 32 | }, 33 | "optionalDependencies": { 34 | "@journeyapps/sqlcipher": "^5.3.1" 35 | }, 36 | "devDependencies": { 37 | "@applint/spec": "^1.2.3", 38 | "@electron/asar": "3", 39 | "@types/jest": "^26.0.24", 40 | "@types/lodash": "^4.14.181", 41 | "@types/node": "^18.15.11", 42 | "cross-env": "^7.0.3", 43 | "dexie": "^3.2.3", 44 | "electron": "19", 45 | "electron-builder": "^24.6.4", 46 | "electron-installer-dmg": "^4.0.0", 47 | "eslint": "7", 48 | "eslint-config-egg": "^5.1.1", 49 | "eslint-plugin-mocha": "^4.11.0", 50 | "git-contributor": "*", 51 | "gulp": "^4.0.2", 52 | "gulp-nodemon": "^2.5.0", 53 | "husky": "4", 54 | "lint-staged": "^13.2.1", 55 | "mocha": "*", 56 | "monitor.js": "^2.0.1", 57 | "nyc": "^15.1.0", 58 | "startserver": "1", 59 | "ts-jest": "^26.5.6", 60 | "ts-loader": "^9.2.8", 61 | "ts-node": "^10.7.0", 62 | "typescript": "^4.6.3" 63 | }, 64 | "scripts": { 65 | "dev": "gulp dev-app", 66 | "dev:watch": "gulp dev-app --watch", 67 | "test": "nyc --reporter=lcov --reporter=text mocha", 68 | "lint": "eslint . --fix", 69 | "tsc": "tsc -p tsconfig.json", 70 | "build": "npm run tsc && npm run build:main", 71 | "build:main": "electron-builder build --config .electron-builder.config.js", 72 | "reset:db": "sh ./scripts/clean-db.sh", 73 | "translate": "easy-i18n-cli -c ./i18n.config.js", 74 | "translate:check": "npm run translate -- --check", 75 | "contributor": "git-contributor", 76 | "ss": "python3 -m http.server 8888" 77 | }, 78 | "husky": { 79 | "hooks": { 80 | "pre-commit": "npm run lint" 81 | } 82 | }, 83 | "license": "MIT" 84 | } 85 | -------------------------------------------------------------------------------- /scripts/build-asar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const asar = require('@electron/asar'); 5 | 6 | const targetPath = path.resolve(__dirname, '..', 'fixtures'); 7 | 8 | const srcPath = path.join(targetPath, 'asar_dir'); 9 | const asarPath = path.join(targetPath, 'demo.asar'); 10 | 11 | async function main() { 12 | return await asar.createPackage(srcPath, asarPath); 13 | } 14 | 15 | main() 16 | .then(res => { 17 | console.log('\nasar path: %s\n', res.dmgPath); 18 | }) 19 | .catch((e) => { 20 | console.error(e); 21 | }); 22 | -------------------------------------------------------------------------------- /scripts/build-dmg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const createDMG = require('electron-installer-dmg'); 5 | 6 | const targetPath = path.resolve(__dirname, '..', 'fixtures'); 7 | const appPath = path.resolve(targetPath, 'demo.app'); 8 | 9 | async function main() { 10 | return await createDMG({ 11 | appPath, 12 | out: targetPath, 13 | name: 'demo', 14 | overwrite: true, 15 | }); 16 | } 17 | 18 | main() 19 | .then(res => { 20 | console.log('\ndmg path: %s\n', res.dmgPath); 21 | }) 22 | .catch((e) => { 23 | console.error(e); 24 | }); 25 | -------------------------------------------------------------------------------- /scripts/clean-db.sh: -------------------------------------------------------------------------------- 1 | # loop to clean the database file 2 | for FILE in src/local-storage/sqlite/*.db; do 3 | echo > $FILE 4 | git add $FILE 5 | done -------------------------------------------------------------------------------- /scripts/develop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { series } = require('gulp'); 4 | 5 | function devApp(done) { 6 | done(); 7 | } 8 | 9 | devApp.displayName = 'dev-app-scripts'; 10 | 11 | exports.devApp = series( 12 | devApp, 13 | require('./electron').start, 14 | ); 15 | -------------------------------------------------------------------------------- /scripts/electron.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const nodemon = require('gulp-nodemon'); 5 | 6 | function startElectron(done) { 7 | const stream = nodemon({ 8 | script: path.resolve(__dirname, 'launcher.js'), 9 | ext: 'ts js json html', 10 | ignore: [], 11 | watch: [ 12 | 'src', 13 | 'package.json', 14 | ], 15 | done, 16 | delay: '5000', 17 | }); 18 | stream 19 | .on('restart', files => { 20 | console.log('App restarted due to: ', files); 21 | }) 22 | .on('crash', () => { 23 | console.error('Application has crashed!\n'); 24 | stream.emit('restart', 10); 25 | }); 26 | } 27 | 28 | startElectron.displayName = 'start-electron'; 29 | 30 | exports.start = startElectron; -------------------------------------------------------------------------------- /scripts/launcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { spawn } = require('child_process'); 4 | const electron = require('electron'); 5 | 6 | spawn(electron, [ 7 | '-r', 8 | 'ts-node/register/transpile-only', 9 | './src/main.ts', 10 | '--no-sandbox', 11 | ], { 12 | env: { ...process.env, NODE_ENV: 'development' }, 13 | stdio: 'inherit', 14 | }); 15 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { ipcMain, dialog, shell } from 'electron'; 2 | 3 | export default class App { 4 | init() { 5 | this.isWin = process.platform === 'win32'; 6 | require('./window-manager')(this); 7 | this.bindIPC(); 8 | } 9 | 10 | alertWindows() { 11 | dialog.showErrorBox('error', 'only windows'); 12 | } 13 | 14 | bindIPC() { 15 | const { isWin } = this; 16 | ipcMain.on('openExternal', (_, data) => { 17 | shell.openExternal(data); 18 | }); 19 | ipcMain.on('start-action', (_, action) => { 20 | if (action === 'electrom') { 21 | require('./electrom')(this); 22 | } else if (action === 'electron-windows') { 23 | require('./multi-windows')(this); 24 | } else if (action === 'electron-updator') { 25 | require('./updator')(this); 26 | } else if (action === 'electron-webview-schedule') { 27 | require('./webview-schedule')(this); 28 | } else if (action === 'electron-windows-titlebar') { 29 | if (isWin) { 30 | require('./windows-titlebar')(this); 31 | return; 32 | } 33 | this.alertWindows(); 34 | } else if (action === 'network-interface') { 35 | if (isWin) { 36 | require('./network-interface')(this); 37 | return; 38 | } 39 | this.alertWindows(); 40 | } else if (action === 'windows-verify-trust') { 41 | if (isWin) { 42 | require('./windows-verify-trust')(this); 43 | return; 44 | } 45 | this.alertWindows(); 46 | } else if (action === 'local-storage') { 47 | require('./local-storage')(this); 48 | } 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/electrom.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { BrowserWindow } = require('electron'); 4 | const Electrom = require('electrom'); 5 | 6 | // https://github.com/electron-modules/electrom 7 | 8 | module.exports = (app) => { 9 | const { 10 | BROWSER_WINDOW_ASSETS_PATH, 11 | BROWSER_WINDOW_PRELOAD_PATH, 12 | EVENT_DATA_CHANNEL_NAME, 13 | } = Electrom; 14 | // 1. initial monitor 15 | const monitor = new Electrom.Monitor({ 16 | interval: 3E3, 17 | }); 18 | // 2. initial window 19 | const win = new BrowserWindow({ 20 | width: 1280, 21 | height: 600, 22 | webPreferences: { 23 | preload: BROWSER_WINDOW_PRELOAD_PATH, 24 | }, 25 | }); 26 | app.windowManager.create({ 27 | name: 'electrom', 28 | browserWindow: { 29 | width: 1280, 30 | height: 800, 31 | title: '', 32 | show: false, 33 | acceptFirstMouse: true, 34 | webPreferences: { 35 | preload: BROWSER_WINDOW_PRELOAD_PATH, 36 | }, 37 | }, 38 | }); 39 | // 3. load electrom assets 40 | win.loadURL(BROWSER_WINDOW_ASSETS_PATH); 41 | win.webContents.on('dom-ready', () => { 42 | // 4. bind monitor event when dom ready 43 | monitor.removeAllListeners(EVENT_DATA_CHANNEL_NAME); 44 | monitor.on(EVENT_DATA_CHANNEL_NAME, (data) => { 45 | if (win && !win.isDestroyed() && win.webContents && !win.webContents.isDestroyed()) { 46 | win.webContents.send(EVENT_DATA_CHANNEL_NAME, data); 47 | } 48 | }); 49 | monitor.bindEventToWindow(win); 50 | monitor.start(); 51 | }); 52 | win.once('ready-to-show', () => { 53 | win.show(); 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /src/local-storage/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const url = require('url'); 4 | const path = require('path'); 5 | const { ipcMain } = require('electron'); 6 | require('@electron/remote/main').initialize(); 7 | 8 | let dbInstance: null | any = null; 9 | function initDB(dbFilePath: string) { 10 | if (!dbInstance) { 11 | const sqlite3 = require('@journeyapps/sqlcipher').verbose(); 12 | dbInstance = new sqlite3.Database(dbFilePath); 13 | 14 | // dbInstance.serialize(() => { 15 | // // This is the default, but it is good to specify explicitly: 16 | // dbInstance.run('PRAGMA cipher_compatibility = 4'); 17 | 18 | // dbInstance.run("PRAGMA key = 'mysecret'"); 19 | // dbInstance.run('CREATE TABLE lorem (info TEXT)'); 20 | // }); 21 | } 22 | return dbInstance; 23 | } 24 | 25 | module.exports = (app: any) => { 26 | const mainUrl = url.format({ 27 | pathname: path.join(__dirname, 'renderer', 'main.html'), 28 | protocol: 'file:', 29 | }); 30 | const loadingUrl = url.format({ 31 | pathname: path.join(__dirname, 'renderer', 'loading.html'), 32 | protocol: 'file:', 33 | }); 34 | const windowSize = { 35 | width: 1280, 36 | height: 800, 37 | }; 38 | const window = app.windowManager.create({ 39 | name: 'local-storage', 40 | loadingView: { 41 | url: loadingUrl, 42 | }, 43 | browserWindow: { 44 | ...windowSize, 45 | title: 'NoSQL Storage', 46 | show: true, 47 | acceptFirstMouse: true, 48 | webPreferences: { 49 | enableRemoteModule: false, 50 | nodeIntegration: false, 51 | webSecurity: true, 52 | webviewTag: true, 53 | preload: path.join(__dirname, 'renderer', 'preload.js'), 54 | }, 55 | }, 56 | openDevTools: true, 57 | }); 58 | window.loadURL(mainUrl); 59 | 60 | // browserWin -> renderer -> main 进程执行 sqlite 操作 61 | ipcMain.on('sqlite:operate', (_, data) => { 62 | const { action, operator = 'run', id, sqlArgs } = data as any; 63 | if (action === 'connect') { 64 | dbInstance = initDB('./src/local-storage/sqlite/test-main.db'); 65 | } 66 | 67 | if (action === 'exec') { 68 | if (dbInstance) { 69 | dbInstance.serialize(() => { 70 | dbInstance[operator](...sqlArgs, (err: Error, row: any) => { 71 | if (err) { 72 | window.webContents.send('sqlite:operate:reply', { 73 | id, 74 | status: 'failed', 75 | cause: err?.message, 76 | }); 77 | return; 78 | } 79 | 80 | window.webContents.send('sqlite:operate:reply', { 81 | id, 82 | result: row, 83 | status: 'success', 84 | }); 85 | }); 86 | }); 87 | } 88 | } 89 | 90 | if (action === 'close') { 91 | if (dbInstance) { 92 | dbInstance.close(); 93 | } 94 | } 95 | }); 96 | }; 97 | -------------------------------------------------------------------------------- /src/local-storage/renderer/indexeddb-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | window.indexedDBHelper = { 4 | deleteAllIndexedDB: async function () { 5 | const dbs = await window.indexedDB.databases(); 6 | for (const db of dbs) { 7 | console.log('delete db start:', db); 8 | try { 9 | const res = await this.deleteDatabase(db.name); 10 | console.log('delete db success:', res); 11 | } catch (e) { 12 | console.log('delete db failed', e); 13 | } 14 | } 15 | return true; 16 | }, 17 | deleteDatabase: (name) => { 18 | return new Promise(((resolve, reject) => { 19 | const request = window.indexedDB.deleteDatabase(name); 20 | request.onsuccess = (event) => { 21 | resolve(event); 22 | }; 23 | request.onerror = (event) => { 24 | reject(event.target.error); 25 | }; 26 | request.onblocked = (event) => { 27 | reject(event.target.error); 28 | }; 29 | })); 30 | }, 31 | getObjectStore: (dbName, dbVersion = 1, storeName) => { 32 | return new Promise(((resolve, reject) => { 33 | const request = window.indexedDB.open(dbName, dbVersion); 34 | request.onerror = (event) => { 35 | reject(event.target.error); 36 | }; 37 | request.onsuccess = (event) => { 38 | const db = event.target.result; 39 | console.log('onsuccess', db.version, db.objectStoreNames); 40 | const objectStore = db.transaction(storeName, 'readwrite') 41 | .objectStore(storeName); 42 | resolve(objectStore); 43 | }; 44 | request.onupgradeneeded = (event) => { 45 | const db = event.target.result; 46 | console.log('onupgradeneeded', db.version, db.objectStoreNames); 47 | let objectStore; 48 | if (db.objectStoreNames.contains(storeName)) { 49 | objectStore = db.transaction(storeName, 'readwrite') 50 | .objectStore(storeName); 51 | } else { 52 | objectStore = db.createObjectStore(storeName, { 53 | autoIncrement: true, 54 | }); 55 | } 56 | resolve(objectStore); 57 | }; 58 | })); 59 | }, 60 | addBatchTestData: (objectStore, count = 1000) => { 61 | return new Promise((resolve, reject) => { 62 | console.time('addBatchTestData'); 63 | for (let i = 1; i <= count; i++) { 64 | const data = { 65 | index1: `index_${i}`, 66 | field1: new Array(100).fill('测试').join(''), 67 | }; 68 | const request = objectStore.add(data); 69 | request.onerror = (event) => { 70 | console.error('Error adding data:', event); 71 | }; 72 | request.onsuccess = () => { 73 | console.log('Data added successfully'); 74 | }; 75 | } 76 | objectStore.transaction.oncomplete = () => { 77 | console.log('Transaction completed'); 78 | console.timeEnd('addBatchTestData'); 79 | resolve(true); 80 | }; 81 | objectStore.transaction.onerror = (event) => { 82 | console.error('Transaction failed:'); 83 | reject(event.target.error); 84 | }; 85 | }); 86 | }, 87 | }; 88 | -------------------------------------------------------------------------------- /src/local-storage/renderer/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading 7 | 28 | 29 | 30 |
31 |
32 | loading... 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/local-storage/renderer/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | #perf-board { 6 | position: fixed; 7 | width: 204px; 8 | top: 8px; 9 | right: 8px; 10 | } 11 | 12 | #wrapper { 13 | margin: 0 auto; 14 | width: 80%; 15 | margin-top: 80px; 16 | } 17 | 18 | .logs { 19 | height: 300px; 20 | overflow-y: scroll; 21 | } 22 | 23 | .content { 24 | display: flex; 25 | } 26 | 27 | .content-item { 28 | flex: 1; 29 | border: 1px solid gray; 30 | padding: 8px; 31 | } 32 | 33 | .content-item .logs { 34 | min-height: 90px; 35 | margin: 12px 4px; 36 | padding: 8px; 37 | border: 1px solid gray; 38 | } 39 | 40 | .content-item .switch fieldset { 41 | min-height: 140px; 42 | } 43 | 44 | .content-item button { 45 | margin-top: 12px; 46 | } 47 | 48 | #others { 49 | margin-top: 12px; 50 | } 51 | -------------------------------------------------------------------------------- /src/local-storage/renderer/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Storage Benchmarks 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | IndexedDB 20 |
21 | 28 | 31 |
32 |
33 | 39 | 42 |
43 |
44 | 50 | 51 |
52 |
53 | 59 | 60 |
61 |
62 | 68 | 69 |
70 |
71 |
72 |
73 |

 74 |           
75 | 76 |
77 |
78 |
79 |
80 |
81 |
82 | SQLite 83 |
84 | 91 | 92 |
93 |
94 | 100 | 101 |
102 |
103 | 109 | 110 |
111 |
112 | 118 | 119 |
120 |
121 |
122 |
123 |

124 |           
125 | 126 |
127 |
128 |
129 |
130 | 147 |
148 |
149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/local-storage/renderer/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)); 4 | 5 | class MainApp { 6 | constructor() { 7 | this.perfboardElemContainer = document.querySelector('#perf-board'); 8 | this.SQLiteLogsContainer = document.querySelector('#SQLiteLogs'); 9 | this.IndexedDBLogsContainer = document.querySelector('#IndexedDBLogs'); 10 | this.runOptions = { 11 | IndexedDB: 'Default', 12 | SQLite: 'ElectronNativeInRenderer', 13 | }; 14 | this.logs = { 15 | IndexedDB: [], 16 | SQLite: [], 17 | }; 18 | this.runnerConfig = { 19 | count: 10 * 10000, 20 | }; 21 | this.init(); 22 | } 23 | 24 | init() { 25 | this.initPFSBoard(); 26 | this.bindEvents(); 27 | } 28 | 29 | initPFSBoard() { 30 | const { Timer, FPSBoard, MemoryStats } = window.monitor; 31 | const stats = new MemoryStats({ 32 | containerWidth: 120, 33 | }); 34 | stats.domElement.style.position = 'absolute'; 35 | stats.domElement.style.right = '0px'; 36 | stats.domElement.style.top = '0px'; 37 | this.perfboardElemContainer.appendChild(stats.domElement); 38 | const fpsBoard_1 = new FPSBoard({ 39 | container: this.perfboardElemContainer, 40 | containerStyles: { 41 | position: 'absolute', 42 | right: 120, 43 | }, 44 | }); 45 | const timer = new Timer(); 46 | timer.update(() => { 47 | fpsBoard_1.tick(); 48 | stats.update(); 49 | }); 50 | timer.start(); 51 | } 52 | 53 | bindEvents() { 54 | document.addEventListener('click', e => { 55 | const { target } = e; 56 | if (target.nodeName === 'A') { 57 | if (e.defaultPrevented) return; 58 | if (target.href) { 59 | e.preventDefault(); 60 | window._electron_bridge.ipcRenderer.send('openExternal', target.href); 61 | } 62 | } else if (target.nodeName === 'BUTTON') { 63 | const type = e.target.getAttribute('data-type'); 64 | this.run(type); 65 | } 66 | }, false); 67 | document.querySelectorAll('form') 68 | .forEach(form => { 69 | form.addEventListener('change', (e) => { 70 | this.runOptions[e.target.name] = e.target.value; 71 | }, false); 72 | }); 73 | } 74 | 75 | run(type) { 76 | const method = `run${type}${this.runOptions[type]}`; 77 | this.log(type, `type: ${type} options: ${this.runOptions[type]}`); 78 | this.log(type, `start run: ${method}`); 79 | this[method]?.(type); 80 | } 81 | 82 | async runIndexedDBDefault(type) { 83 | await window.indexedDBHelper.deleteAllIndexedDB(); 84 | const storeName = 'myData1'; 85 | const objectStore = await window.indexedDBHelper.getObjectStore('myDatabase1', 10, storeName); 86 | const { count } = this.runnerConfig; 87 | const startTime = Date.now(); 88 | this.log(type, `start time: ${startTime}, count: ${count}`); 89 | await window.indexedDBHelper.addBatchTestData(objectStore, count); 90 | const endTime = Date.now(); 91 | this.log(type, `end time: ${endTime}, use ${((endTime - startTime) / 1000).toFixed(2)}s`); 92 | } 93 | 94 | async runIndexedDBDexie(type) { 95 | await window.indexedDBHelper.deleteAllIndexedDB(); 96 | const storeName = 'myData1'; 97 | const db = new window.Dexie('myDatabase2'); 98 | db.version(1).stores({ 99 | [storeName]: 'index1, field1', 100 | }); 101 | const { count } = this.runnerConfig; 102 | const startTime = Date.now(); 103 | this.log(type, `start time: ${startTime}, count: ${count}`); 104 | for (let i = 0; i < count; i++) { 105 | await db[storeName].add({ 106 | index1: `index_${i}`, 107 | field1: new Array(100).fill('测试').join(''), 108 | }); 109 | console.log('Data added successfully'); 110 | } 111 | const endTime = Date.now(); 112 | this.log(type, `end time: ${endTime}, use ${((endTime - startTime) / 1000).toFixed(2)}s`); 113 | } 114 | 115 | async runIndexedDBLoveField(type) { 116 | await window.indexedDBHelper.deleteAllIndexedDB(); 117 | 118 | const schemaBuilder = lf.schema.create('todo', 1); 119 | schemaBuilder.createTable('Item') 120 | .addColumn('id', lf.Type.INTEGER) 121 | .addColumn('description', lf.Type.STRING) 122 | .addPrimaryKey(['id']); 123 | 124 | const db = await schemaBuilder.connect(); 125 | db.getSchema().table('Item'); 126 | 127 | const { count } = this.runnerConfig; 128 | const startTime = Date.now(); 129 | this.log(type, `start time: ${startTime}, count: ${count}`); 130 | for (let i = 0; i < count; i++) { 131 | const item = db.getSchema().table('Item'); 132 | const row = item.createRow({ 133 | id: i, 134 | description: new Array(100).fill('测试').join(''), 135 | }); 136 | 137 | await db.insertOrReplace().into(item).values([row]).exec(); 138 | 139 | console.log('Data added successfully'); 140 | } 141 | const endTime = Date.now(); 142 | this.log(type, `end time: ${endTime}, use ${((endTime - startTime) / 1000).toFixed(2)}s`); 143 | } 144 | 145 | async runSQLiteWASM(type) { 146 | const sqlPromise = initSqlJs({ 147 | // Required to load the wasm binary asynchronously. Of course, you can host it wherever you want 148 | // You can omit locateFile completely when running in node 149 | locateFile: () => '../../../node_modules/sql.js/dist/sql-wasm.wasm', 150 | }); 151 | 152 | const dataPromise = fetch('../sqlite/test-sql.js.db').then(res => res.arrayBuffer()); 153 | const [SQL, buf] = await Promise.all([sqlPromise, dataPromise]); 154 | const db = new SQL.Database(new Uint8Array(buf)); 155 | 156 | db.run('DROP TABLE IF EXISTS todo;'); 157 | db.run('CREATE TABLE todo (id int, description varchar);'); 158 | 159 | const { count } = this.runnerConfig; 160 | const startTime = Date.now(); 161 | this.log(type, `start time: ${startTime}, count: ${count}`); 162 | for (let i = 0; i < count; i++) { 163 | db.run('INSERT INTO todo VALUES (?,?)', [i, new Array(100).fill('测试').join('')]); 164 | console.log('Data added successfully'); 165 | } 166 | const endTime = Date.now(); 167 | this.log(type, `end time: ${endTime}, use ${((endTime - startTime) / 1000).toFixed(2)}s`); 168 | this._dump(db); 169 | } 170 | 171 | _dump(db) { 172 | // core dump as file 173 | const binaryArray = db.export(); 174 | // download this binaryArray as a file 175 | const blob = new Blob([binaryArray], { type: 'application/octet-stream' }); 176 | const url = URL.createObjectURL(blob); 177 | const a = document.createElement('a'); 178 | a.download = 'test-sql.js.db'; 179 | a.href = url; 180 | a.click(); 181 | 182 | // const [fileHandle] = await window.showOpenFilePicker({ 183 | // types: [ 184 | // { 185 | // description: 'sql.js dump file', 186 | // accept: { 187 | // 'text/plain': ['.db'], 188 | // }, 189 | // }, 190 | // ], 191 | // }); 192 | // const writable = await fileHandle.createWritable(); 193 | // // Write the contents of the file to the stream. 194 | // await writable.write(binaryArray); 195 | // // Close the file and write the contents to disk. 196 | // await writable.close(); 197 | } 198 | 199 | async runSQLiteWASMInWorker(type) { 200 | const worker = new Worker('../../../node_modules/sql.js/dist/worker.sql-wasm.js'); 201 | 202 | worker.onmessage = () => { 203 | console.log('Database opened'); 204 | worker.onmessage = event => { 205 | console.log('Data added successfully'); 206 | if (event.data?.id === 'end') { 207 | const endTime = Date.now(); 208 | this.log(type, `end time: ${endTime}, use ${((endTime - startTime) / 1000).toFixed(2)}s`); 209 | } 210 | }; 211 | 212 | worker.postMessage({ 213 | id: 'drop', 214 | action: 'exec', 215 | sql: 'DROP TABLE IF EXISTS todo_worker;', 216 | }); 217 | 218 | // create table 219 | worker.postMessage({ 220 | id: 'init', 221 | action: 'exec', 222 | sql: 'CREATE TABLE todo_worker (id int, description varchar);', 223 | }); 224 | 225 | const { count } = this.runnerConfig; 226 | const startTime = Date.now(); 227 | this.log(type, `start time: ${startTime}, count: ${count}`); 228 | for (let i = 0; i < count; i++) { 229 | worker.postMessage({ 230 | id: i, 231 | action: 'exec', 232 | sql: 'INSERT INTO todo_worker VALUES ($id, $description)', 233 | params: { $id: i, $description: new Array(100).fill('测试').join('') }, 234 | }); 235 | } 236 | 237 | // 以 count 结束 238 | worker.postMessage({ 239 | id: 'end', 240 | action: 'exec', 241 | sql: 'select count(*) from todo_worker', 242 | }); 243 | }; 244 | 245 | worker.onerror = e => console.log('Worker error: ', e); 246 | 247 | const buf = await fetch('../sqlite/test-sql.js-worker.db') 248 | .then(res => res.arrayBuffer()); 249 | 250 | worker.postMessage({ 251 | id: 'open', 252 | action: 'open', 253 | buffer: buf, 254 | }); 255 | } 256 | 257 | async runSQLiteElectronNativeInRenderer(type) { 258 | const { count } = this.runnerConfig; 259 | const startTime = Date.now(); 260 | this.log(type, `start time: ${startTime}, count: ${count}`); 261 | await window._electron_bridge.sqlConnect(); 262 | await window._electron_bridge.sqlExec( 263 | 'run', 264 | 'DROP TABLE IF EXISTS todo_electron_native;', 265 | ); 266 | await window._electron_bridge.sqlExec( 267 | 'run', 268 | 'CREATE TABLE todo_electron_native (id int, description varchar);', 269 | ); 270 | 271 | for (let i = 0; i < count; i++) { 272 | await window._electron_bridge.sqlExec( 273 | 'run', 274 | 'INSERT INTO todo_electron_native VALUES (?,?)', 275 | [i, new Array(100).fill('测试').join('')], 276 | ); 277 | console.log('Data added successfully'); 278 | } 279 | 280 | // 轮训等待写入完成 281 | let cnt; 282 | while (cnt !== count) { 283 | const res = await window._electron_bridge.sqlExec( 284 | 'get', 285 | 'SELECT count(*) as cnt FROM todo_electron_native', 286 | ); 287 | if (res.cnt === count) { 288 | break; 289 | } else { 290 | await sleep(50); 291 | } 292 | } 293 | 294 | const endTime = Date.now(); 295 | this.log(type, `end time: ${endTime}, use ${((endTime - startTime) / 1000).toFixed(2)}s`); 296 | // await window._electron_bridge.sqlClose(); 297 | } 298 | 299 | async runSQLiteElectronIPCToMain(type) { 300 | const { count } = this.runnerConfig; 301 | const startTime = Date.now(); 302 | this.log(type, `start time: ${startTime}, count: ${count}`); 303 | 304 | await this.connectIpcRenderAsync('sqlite:operate', 'connect', { 305 | action: 'connect', 306 | }); 307 | 308 | await this.connectIpcRenderAsync('sqlite:operate', 'drop', { 309 | action: 'exec', 310 | sqlArgs: ['DROP TABLE IF EXISTS todo_electron_ipc;'], 311 | }); 312 | 313 | await this.connectIpcRenderAsync('sqlite:operate', 'init', { 314 | action: 'exec', 315 | sqlArgs: ['CREATE TABLE todo_electron_ipc (id int, description varchar);'], 316 | }); 317 | 318 | for (let i = 0; i < count; i++) { 319 | // 无法确保已经写入成功,只能发送消息成功 320 | await this.connectIpcRenderAsync('sqlite:operate', `insert${i}`, { 321 | action: 'exec', 322 | sqlArgs: [ 323 | 'INSERT INTO todo_electron_ipc VALUES (?,?)', 324 | [i, new Array(100).fill('测试').join('')], 325 | ], 326 | }); 327 | 328 | console.log('Data added successfully'); 329 | } 330 | 331 | // 轮训等待写入完成 332 | let cnt; 333 | while (cnt !== count) { 334 | const res = await this.connectIpcRenderAsync('sqlite:operate', 'select_total', { 335 | action: 'exec', 336 | operator: 'get', 337 | sqlArgs: [ 338 | 'SELECT count(*) as cnt FROM todo_electron_ipc', 339 | ], 340 | }, { needRes: true }); 341 | if (res.cnt === count) { 342 | break; 343 | } else { 344 | await sleep(50); 345 | } 346 | } 347 | 348 | const endTime = Date.now(); 349 | this.log(type, `end time: ${endTime}, use ${((endTime - startTime) / 1000).toFixed(2)}s`); 350 | 351 | // await this.connectIpcRenderAsync('sqlite:operate', 'close', { 352 | // action: 'close', 353 | // }); 354 | } 355 | 356 | async connectIpcRenderAsync(channel, msgId, args, options = { needRes: false }) { 357 | // 直接发送 358 | if (!options.needRes) { 359 | window._electron_bridge.ipcRenderer.send(channel, { 360 | ...args, 361 | id: msgId, 362 | }); 363 | return Promise.resolve(); 364 | } 365 | 366 | return new Promise((resolve, reject) => { 367 | window._electron_bridge.ipcRenderer.on(`${channel}:reply`, (event, { id, result, status, cause }) => { 368 | if (msgId === id) { 369 | if (status === 'success') { 370 | resolve(result); 371 | } else { 372 | reject(new Error(cause)); 373 | } 374 | } 375 | }); 376 | 377 | window._electron_bridge.ipcRenderer.send('sqlite:operate', { 378 | ...args, 379 | id: msgId, 380 | }); 381 | }); 382 | } 383 | 384 | log(type, content = '') { 385 | this.logs[type].unshift(content); 386 | this[`${type}LogsContainer`].innerHTML = this.logs[type].join('\n'); 387 | } 388 | } 389 | 390 | window.mainApp = new MainApp(); 391 | -------------------------------------------------------------------------------- /src/local-storage/renderer/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { contextBridge, ipcRenderer } = require('electron'); 4 | const sqlite3 = require('@journeyapps/sqlcipher').verbose(); 5 | 6 | let dbInstance; 7 | function initDB(dbFilePath) { 8 | if (!dbInstance) { 9 | dbInstance = new sqlite3.Database(dbFilePath); 10 | } 11 | return dbInstance; 12 | } 13 | 14 | contextBridge.exposeInMainWorld('_electron_bridge', { 15 | ipcRenderer: { 16 | send: (channel, ...args) => ipcRenderer.send(channel, ...args), 17 | on: (channel, ...args) => ipcRenderer.on(channel, ...args), 18 | }, 19 | // browserWin -> renderer 进程执行 sqlite 操作 20 | sqlExec: async (operator, ...args) => { 21 | // 转为 Promise 22 | operator = operator || 'run'; 23 | return new Promise((resolve, reject) => { 24 | if (dbInstance) { 25 | dbInstance.serialize(() => { 26 | dbInstance[operator](...args, (err, result) => { 27 | if (err) { 28 | reject(err); 29 | } 30 | // 确保写入后再 resolve 31 | resolve(result); 32 | }); 33 | }); 34 | } else { 35 | reject('dbInstance is not existed'); 36 | } 37 | }); 38 | }, 39 | sqlConnect: async () => { 40 | // 初始化 41 | dbInstance = initDB('./src/local-storage/sqlite/test-renderer.db'); 42 | 43 | return 'ok'; 44 | }, 45 | sqlClose: async () => { 46 | dbInstance?.close(); 47 | return 'ok'; 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /src/local-storage/sqlite.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-modules/electron-modules-sample/e328fccb752bc8d3f3ab4b6b6c1d9c3c5cbd0e24/src/local-storage/sqlite.ts -------------------------------------------------------------------------------- /src/local-storage/sqlite/test-main.db: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/local-storage/sqlite/test-renderer.db: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/local-storage/sqlite/test-sql.js-worker.db: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/local-storage/sqlite/test-sql.js.db: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { app as electronApp, dialog } from 'electron'; 2 | import { __i18n } from './preload'; 3 | import App from './app'; 4 | 5 | electronApp.whenReady() 6 | .then(() => { 7 | const app = new App(); 8 | app.init(); 9 | }) 10 | .catch(() => { 11 | dialog.showErrorBox(__i18n('系统提示'), __i18n('启动失败')); 12 | }); 13 | -------------------------------------------------------------------------------- /src/multi-windows/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const url = require('url'); 4 | const path = require('path'); 5 | const _ = require('lodash'); 6 | const { ipcMain, screen, BrowserWindow } = require('electron'); 7 | 8 | const getRandomPostion = () => { 9 | const { workAreaSize } = screen.getPrimaryDisplay(); 10 | const { width: screenWidth, height: screenHeight } = workAreaSize; 11 | const x = parseInt(_.random(screenWidth / 16, screenWidth / 2), 10); 12 | const y = parseInt(_.random(screenHeight / 16, screenHeight / 2), 10); 13 | return { 14 | x, 15 | y, 16 | }; 17 | }; 18 | 19 | module.exports = (app: any) => { 20 | const windowUrl = url.format({ 21 | pathname: path.join(__dirname, 'renderer', 'window.html'), 22 | protocol: 'file:', 23 | }); 24 | const loadingUrl = url.format({ 25 | pathname: path.join(__dirname, 'renderer', 'loading.html'), 26 | protocol: 'file:', 27 | }); 28 | 29 | const postion = getRandomPostion(); 30 | const window = app.windowManager.create({ 31 | name: Date.now(), 32 | loadingView: { 33 | url: loadingUrl, 34 | }, 35 | browserWindow: { 36 | x: postion.x, 37 | y: postion.y, 38 | webPreferences: { 39 | nodeIntegration: true, 40 | webSecurity: true, 41 | webviewTag: true, 42 | preload: path.join(__dirname, 'renderer', 'preload.js'), 43 | }, 44 | }, 45 | }); 46 | window.loadURL(windowUrl); 47 | ipcMain.on('close-window', (_) => { 48 | const window = BrowserWindow.fromWebContents(_.sender); 49 | window.close(); 50 | }); 51 | 52 | ipcMain.on('blur-window', (_) => { 53 | const window = BrowserWindow.fromWebContents(_.sender); 54 | window.blur(); 55 | }); 56 | 57 | ipcMain.on('open-devtools', (_) => { 58 | const window = BrowserWindow.fromWebContents(_.sender); 59 | window.openDevTools(); 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /src/multi-windows/renderer/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading 7 | 28 | 29 | 30 |
31 |
32 | loading... 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/multi-windows/renderer/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | ipcRenderer, shell, 5 | desktopCapturer, contextBridge, 6 | } = require('electron'); 7 | 8 | contextBridge.exposeInMainWorld( 9 | '_electron_bridge', 10 | { 11 | send: (channel, args) => { 12 | ipcRenderer.send(channel, args); 13 | }, 14 | desktopCapturer, 15 | }, 16 | ); 17 | -------------------------------------------------------------------------------- /src/multi-windows/renderer/window.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/multi-windows/renderer/window.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | document.querySelector('#close').addEventListener('click', () => { 4 | window._electron_bridge.send('close-window'); 5 | }, false); 6 | 7 | document.querySelector('#blur').addEventListener('click', () => { 8 | window._electron_bridge.send('blur-window'); 9 | }, false); 10 | 11 | document.querySelector('#write').addEventListener('click', () => { 12 | const data = 'write local file successfully'; 13 | const fileName = 'file.txt'; 14 | window._electron_bridge.writeFile(fileName, data, (err) => { 15 | if (err) { 16 | console.log(err); 17 | } else { 18 | console.log(window._electron_bridge.readFileSync(fileName, 'utf8')); 19 | } 20 | }); 21 | }, false); 22 | 23 | document.querySelector('#debug').addEventListener('click', () => { 24 | window._electron_bridge.send('open-devtools'); 25 | }, false); 26 | 27 | document.addEventListener('click', e => { 28 | const { target } = e; 29 | if (target.nodeName === 'A') { 30 | if (e.defaultPrevented) return; 31 | if (target.href) { 32 | e.preventDefault(); 33 | window._electron_bridge.ipcRenderer.send('openExternal', target.href); 34 | } 35 | } 36 | }, false); 37 | -------------------------------------------------------------------------------- /src/network-interface/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import networkInterface from 'network-interface'; 4 | 5 | module.exports = (app: any) => { 6 | networkInterface.addEventListener('wlan-status-changed', (error, data) => { 7 | if (error) { 8 | throw error; 9 | return; 10 | } 11 | console.log('event fired: wlan-status-changed'); 12 | console.log(data); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /src/preload.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import locale from 'easy-i18n-cli/src/locale'; 4 | import enObj from '../en-US'; 5 | 6 | export const __i18n = locale({ 7 | en: enObj, 8 | useEn: () => true, 9 | }); 10 | -------------------------------------------------------------------------------- /src/renderer/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading 7 | 28 | 29 | 30 |
31 |
32 | loading... 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/renderer/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Electron Modules Sample 7 | 52 | 53 | 54 |
55 |

Electron Modules

56 | 57 | https://github.com/electron-modules 58 | 59 |
60 |
    61 |
  1. 🐛 Electrom
  2. 62 |
  3. 🚀 Electron Windows
  4. 63 |
  5. ⭐️ Electron Updator
  6. 64 |
  7. 🌏 Electron Webview Schedule
  8. 65 |
  9. 🌓 Electron Windows Titlebar
  10. 66 |
  11. 💻 Network Interface
  12. 67 |
  13. 🛡 Windows Verify Trust
  14. 68 |
  15. 📚 Local Storage
  16. 69 |
  17. 📘 CrashPad
  18. 70 |
      71 |
72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | document.querySelectorAll('[data-action]').forEach(elem => { 4 | elem.addEventListener('click', () => { 5 | const action = elem.getAttribute('data-action'); 6 | window._electron_bridge.ipcRenderer.send('start-action', action); 7 | }); 8 | }); 9 | 10 | document.addEventListener('click', e => { 11 | const { target } = e; 12 | if (target.nodeName === 'A') { 13 | if (e.defaultPrevented) return; 14 | if (target.href) { 15 | e.preventDefault(); 16 | window._electron_bridge.ipcRenderer.send('openExternal', target.href); 17 | } 18 | } 19 | }, false); 20 | -------------------------------------------------------------------------------- /src/renderer/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | ipcRenderer, 5 | desktopCapturer, contextBridge, 6 | } = require('electron'); 7 | 8 | contextBridge.exposeInMainWorld('_electron_bridge', { 9 | ipcRenderer: { 10 | send: (channel, ...args) => ipcRenderer.send(channel, ...args), 11 | }, 12 | desktopCapturer, 13 | }); 14 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'electron-windows'; 2 | declare module 'easy-i18n-cli/src/locale'; 3 | -------------------------------------------------------------------------------- /src/updator/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import url from 'url'; 4 | import path from 'path'; 5 | import semver from 'semver'; 6 | import { ipcMain, dialog } from 'electron'; 7 | import ElectronUpdator from 'graceful-updater'; 8 | import { version as ElectronUpdatorVersion } from 'graceful-updater/package'; 9 | 10 | console.log('version: %s', ElectronUpdatorVersion); 11 | 12 | const { MacUpdator, EventType } = ElectronUpdator; 13 | 14 | // npm run ss 15 | function getFeedUrl(feedUrlTag = 'asar1') { 16 | const feedUrlsMap = { 17 | asar1: 'http://localhost:8888/fixtures/data/asar1.json', 18 | asar2: 'http://localhost:8888/fixtures/data/asar2.json', 19 | package1: 'http://localhost:8888/fixtures/data/package1.json', 20 | }; 21 | return feedUrlsMap[feedUrlTag]; 22 | } 23 | 24 | const currentVersion = '0.0.2'; 25 | const currentBuildNumber = 2; 26 | 27 | module.exports = (app: any) => { 28 | // 1. 构造 options 29 | const options = { 30 | url: getFeedUrl(), 31 | logger: console, // logger 32 | productName: 'demo', 33 | updateInfoFormatter: (res) => { 34 | return res; 35 | }, 36 | ifNeedUpdate: (res) => { 37 | console.log('local version', currentVersion); 38 | console.log('local project version', currentBuildNumber); 39 | console.log('remote version', res.version); 40 | console.log('remote project version', res.project_version); 41 | return semver.gt(res.version, currentVersion) || 42 | res.project_version > currentBuildNumber; 43 | }, 44 | }; 45 | // 2. 初始化 updator 实例 46 | const electronUpdator = new MacUpdator(options); 47 | // 3. 绑定全局事件 48 | electronUpdator.on(EventType.UPDATE_DOWNLOADED, (...args) => { 49 | console.log('updator >> %s, args: %j', EventType.UPDATE_DOWNLOADED, args); 50 | }); 51 | electronUpdator.on(EventType.CHECKING_FOR_UPDATE, (...args) => { 52 | console.log('updator >> %s, args: %j', EventType.CHECKING_FOR_UPDATE, args); 53 | }); 54 | electronUpdator.on(EventType.UPDATE_AVAILABLE, (data) => { 55 | const { version, project_version } = data?.updateInfo || {}; 56 | const message = [ 57 | 'available', 58 | `local version: ${currentVersion}`, 59 | `local project version: ${currentBuildNumber}`, 60 | `remote version: ${version}`, 61 | `remote project version: ${project_version}`, 62 | ].join('\n'); 63 | dialog.showMessageBoxSync({ 64 | message, 65 | }); 66 | }); 67 | electronUpdator.on(EventType.UPDATE_NOT_AVAILABLE, (data) => { 68 | const { version, project_version } = data?.updateInfo || {}; 69 | const message = [ 70 | 'not available', 71 | `local version: ${currentVersion}`, 72 | `local project version: ${currentBuildNumber}`, 73 | `remote version: ${version}`, 74 | `remote project version: ${project_version}`, 75 | ].join('\n'); 76 | dialog.showMessageBoxSync({ 77 | message, 78 | }); 79 | }); 80 | electronUpdator.on(EventType.ERROR, (...args) => { 81 | console.log('updator >> %s, args: %j', EventType.ERROR, args); 82 | }); 83 | electronUpdator.on(EventType.UPDATE_DOWNLOAD_PROGRESS, (data) => { 84 | const { status, progress } = data; 85 | console.log('updator >> %s, status: %s, progress: %d', EventType.UPDATE_DOWNLOAD_PROGRESS, status, progress); 86 | app.windowManager.get('updator').webContents.send('updator:updateDownloadProgress', { status, progress }); 87 | }); 88 | app.electronUpdator = electronUpdator; 89 | 90 | ipcMain.on('updator:checkForUpdates:available', () => { 91 | app.electronUpdator.setFeedUrl(getFeedUrl('asar1')); 92 | app.electronUpdator.checkForUpdates(); 93 | }); 94 | 95 | ipcMain.on('updator:checkForUpdates:notAvailable', () => { 96 | app.electronUpdator.setFeedUrl(getFeedUrl('asar2')); 97 | app.electronUpdator.checkForUpdates(); 98 | }); 99 | 100 | ipcMain.on('updator:downloadUpdate', () => { 101 | app.electronUpdator.downloadUpdate(); 102 | }); 103 | 104 | ipcMain.on('updator:quitAndInstall', () => { 105 | app.electronUpdator.quitAndInstall(); 106 | }); 107 | 108 | const mainUrl = url.format({ 109 | pathname: path.join(__dirname, 'renderer', 'main.html'), 110 | protocol: 'file:', 111 | }); 112 | const loadingUrl = url.format({ 113 | pathname: path.join(__dirname, 'renderer', 'loading.html'), 114 | protocol: 'file:', 115 | }); 116 | 117 | const window = app.windowManager.create({ 118 | name: 'updator', 119 | loadingView: { 120 | url: loadingUrl, 121 | }, 122 | browserWindow: { 123 | width: 600, 124 | height: 480, 125 | webPreferences: { 126 | nodeIntegration: true, 127 | webSecurity: true, 128 | webviewTag: true, 129 | preload: path.join(__dirname, 'renderer', 'preload.js'), 130 | }, 131 | }, 132 | }); 133 | window.loadURL(mainUrl); 134 | }; 135 | -------------------------------------------------------------------------------- /src/updator/renderer/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading 7 | 28 | 29 | 30 |
31 |
32 | loading... 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/updator/renderer/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 46 | 47 | 48 |
49 |
    50 |
  1. 51 | checkForUpdates 52 | 53 | 54 |
  2. 55 |
  3. 56 | downloadUpdate 57 | 58 | progress: 0 59 |
  4. 60 |
  5. 61 | quitAndInstall 62 | 63 |
  6. 64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /src/updator/renderer/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | document.querySelector('#checkForUpdates_available').addEventListener('click', () => { 4 | window._electron_bridge.send('updator:checkForUpdates:available'); 5 | }, false); 6 | 7 | document.querySelector('#checkForUpdates_notAvailable').addEventListener('click', () => { 8 | window._electron_bridge.send('updator:checkForUpdates:notAvailable'); 9 | }, false); 10 | 11 | document.querySelector('#downloadUpdate').addEventListener('click', () => { 12 | window._electron_bridge.send('updator:downloadUpdate'); 13 | }, false); 14 | 15 | document.querySelector('#quitAndInstall').addEventListener('click', () => { 16 | window._electron_bridge.send('updator:quitAndInstall'); 17 | }, false); 18 | 19 | window._electron_bridge.on('updator:updateDownloadProgress', (_, data) => { 20 | document.querySelector('#progress').innerHTML = JSON.stringify(data, null, 2); 21 | }); 22 | -------------------------------------------------------------------------------- /src/updator/renderer/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | ipcRenderer, shell, 5 | desktopCapturer, contextBridge, 6 | } = require('electron'); 7 | 8 | contextBridge.exposeInMainWorld( 9 | '_electron_bridge', 10 | { 11 | send: (channel, args) => { 12 | ipcRenderer.send(channel, args); 13 | }, 14 | invoke: ipcRenderer.invoke, 15 | on: (channel, listener) => { 16 | ipcRenderer.on(channel, listener); 17 | }, 18 | desktopCapturer, 19 | }, 20 | ); 21 | -------------------------------------------------------------------------------- /src/webview-schedule/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const url = require('url'); 4 | const path = require('path'); 5 | require('@electron/remote/main').initialize(); 6 | 7 | module.exports = (app) => { 8 | const mainUrl = url.format({ 9 | pathname: path.join(__dirname, 'renderer', 'main.html'), 10 | protocol: 'file:', 11 | }); 12 | const loadingUrl = url.format({ 13 | pathname: path.join(__dirname, 'renderer', 'loading.html'), 14 | protocol: 'file:', 15 | }); 16 | const windowSize = { 17 | width: 1280, 18 | height: 800, 19 | }; 20 | const window = app.windowManager.create({ 21 | name: 'webview', 22 | loadingView: { 23 | url: loadingUrl, 24 | }, 25 | browserWindow: { 26 | ...windowSize, 27 | title: 'webview', 28 | show: true, 29 | acceptFirstMouse: true, 30 | webPreferences: { 31 | enableRemoteModule: false, 32 | nodeIntegration: false, 33 | webSecurity: true, 34 | webviewTag: true, 35 | preload: path.join(__dirname, 'renderer', 'preload.js'), 36 | }, 37 | }, 38 | }); 39 | window.loadURL(mainUrl); 40 | }; 41 | -------------------------------------------------------------------------------- /src/webview-schedule/renderer/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading 7 | 28 | 29 | 30 |
31 |
32 | loading... 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/webview-schedule/renderer/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Main 7 | 49 | 50 | 51 | 52 |
53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/webview-schedule/renderer/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { WebviewSchedule, PromiseQueue, moment } = window; 4 | 5 | const webviewContainer = document.querySelector('#webview'); 6 | 7 | window._webviewManager = new WebviewSchedule({ 8 | container: webviewContainer, 9 | queue: new PromiseQueue(1), 10 | moment, 11 | webviewOptions: { 12 | eventsStack: [], 13 | getSrcFromType: type => { 14 | return `./webview.html?webviewType=${type}`; 15 | }, 16 | preload: './webview-preload.js', 17 | attributes: {}, 18 | }, 19 | }); 20 | 21 | document.querySelector('#add1').addEventListener('click', () => { 22 | window._webviewManager.send('webview1', 'send-to-webview', { 23 | date: moment().format('YYYY-MM-DD HH:mm:ss'), 24 | }); 25 | }, false); 26 | document.querySelector('#add2').addEventListener('click', () => { 27 | window._webviewManager.send('webview2', 'send-to-webview', { 28 | date: moment().format('YYYY-MM-DD HH:mm:ss'), 29 | }); 30 | }, false); 31 | document.querySelector('#add3').addEventListener('click', () => { 32 | window._webviewManager.send('webview3', 'send-to-webview', { 33 | date: moment().format('YYYY-MM-DD HH:mm:ss'), 34 | }); 35 | }, false); 36 | document.querySelector('#add4').addEventListener('click', () => { 37 | window._webviewManager.send('webview4', 'send-to-webview', { 38 | date: moment().format('YYYY-MM-DD HH:mm:ss'), 39 | }); 40 | }, false); 41 | document.querySelector('#add5').addEventListener('click', () => { 42 | window._webviewManager.send('webview5', 'send-to-webview', { 43 | date: moment().format('YYYY-MM-DD HH:mm:ss'), 44 | }); 45 | }, false); 46 | document.querySelector('#clear').addEventListener('click', () => { 47 | window._webviewManager.clearAll(); 48 | }, false); 49 | document.querySelector('#debug').addEventListener('click', () => { 50 | window.electron.openDevTool(); 51 | }, false); 52 | -------------------------------------------------------------------------------- /src/webview-schedule/renderer/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { contextBridge } = require('electron'); 4 | const { getCurrentWindow } = require('@electron/remote'); 5 | 6 | contextBridge.exposeInMainWorld( 7 | 'electron', 8 | { 9 | openDevTool: () => getCurrentWindow().webContents.openDevTools(), 10 | process: { 11 | arch: process.arch, 12 | version: process.version, 13 | versions: process.versions, 14 | }, 15 | }, 16 | ); 17 | 18 | -------------------------------------------------------------------------------- /src/webview-schedule/renderer/webview-preload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { contextBridge, ipcRenderer } = require('electron'); 4 | 5 | contextBridge.exposeInMainWorld( 6 | 'electron', 7 | { 8 | receive: (channel, fn) => { 9 | ipcRenderer.on(channel, (event, ...args) => fn(event, ...args)); 10 | }, 11 | sendToHost: (channel, args) => { 12 | ipcRenderer.sendToHost(channel, args); 13 | }, 14 | crash: () => process.crash(), 15 | hang: () => process.hang(), 16 | }, 17 | ); 18 | -------------------------------------------------------------------------------- /src/webview-schedule/renderer/webview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading 7 | 26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/webview-schedule/renderer/webview.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { moment } = window; 4 | 5 | window.onload = () => { 6 | const container = document.querySelector('#desc'); 7 | const urlParams = new URLSearchParams(window.location.search); 8 | const webviewType = urlParams.get('webviewType'); 9 | const loadedAt = moment().format('YYYY-MM-DD HH:mm:ss'); 10 | let executeNum = -1; 11 | const genDesc = (text = '') => { 12 | executeNum++; 13 | return [ 14 | webviewType, 15 | loadedAt, 16 | text, 17 | executeNum, 18 | ].join('
'); 19 | }; 20 | container.innerHTML = genDesc(); 21 | window.electron.receive('send-to-webview', (_, data) => { 22 | container.innerHTML = genDesc(data.date); 23 | window.electron.sendToHost('send-to-host', { webviewType }); 24 | }); 25 | document.querySelector('#remove').addEventListener('click', () => { 26 | window.electron.sendToHost('remove-webview'); 27 | }, false); 28 | document.querySelector('#crash').addEventListener('click', () => { 29 | window.electron.crash(); 30 | }, false); 31 | document.querySelector('#hang').addEventListener('click', () => { 32 | window.electron.hang(); 33 | }, false); 34 | }; 35 | -------------------------------------------------------------------------------- /src/window-manager.ts: -------------------------------------------------------------------------------- 1 | import url from 'url'; 2 | import path from 'path'; 3 | import WindowManager from 'electron-windows'; 4 | 5 | module.exports = (app) => { 6 | app.windowManager = app.windowManager || new WindowManager(); 7 | 8 | const rendererDir = path.resolve(__dirname, 'renderer'); 9 | const loadingUrl = url.format({ 10 | pathname: path.resolve(rendererDir, 'loading.html'), 11 | protocol: 'file:', 12 | }); 13 | const mainUrl = url.format({ 14 | pathname: path.resolve(rendererDir, 'main.html'), 15 | protocol: 'file:', 16 | }); 17 | const preload = path.resolve(rendererDir, 'preload.js'); 18 | 19 | const mainWindow = app.windowManager.create({ 20 | name: 'main', 21 | loadingView: { 22 | url: loadingUrl, 23 | }, 24 | browserWindow: { 25 | width: 600, 26 | height: 500, 27 | webPreferences: { 28 | enableRemoteModule: false, 29 | nodeIntegration: false, 30 | webSecurity: true, 31 | webviewTag: true, 32 | preload, 33 | }, 34 | }, 35 | openDevTools: false, 36 | }); 37 | mainWindow.loadURL(mainUrl); 38 | }; 39 | -------------------------------------------------------------------------------- /src/windows-titlebar/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import url from 'url'; 4 | import path from 'path'; 5 | import _ from 'lodash'; 6 | import { ipcMain } from 'electron'; 7 | import windowTitleBar from 'electron-windows-titlebar'; 8 | 9 | module.exports = (app: any) => { 10 | const windowUrl = url.format({ 11 | pathname: path.join(__dirname, 'renderer', 'main.html'), 12 | protocol: 'file:', 13 | }); 14 | const loadingUrl = url.format({ 15 | pathname: path.join(__dirname, 'renderer', 'loading.html'), 16 | protocol: 'file:', 17 | }); 18 | 19 | const window = app.windowManager.create({ 20 | name: Date.now(), 21 | loadingView: { 22 | url: loadingUrl, 23 | }, 24 | browserWindow: { 25 | webPreferences: { 26 | nodeIntegration: true, 27 | webSecurity: true, 28 | webviewTag: true, 29 | preload: path.join(__dirname, 'renderer', 'preload.js'), 30 | }, 31 | }, 32 | }); 33 | const hwnd = window?.getNativeWindowHandle(); 34 | ipcMain.on('switch-theme-mode', (_, params) => { 35 | const { dark } = params; 36 | if (hwnd) { 37 | dark ? windowTitleBar.switchDarkMode(hwnd) : windowTitleBar.switchLightMode(hwnd); 38 | } 39 | }); 40 | window.loadURL(windowUrl); 41 | }; 42 | -------------------------------------------------------------------------------- /src/windows-titlebar/renderer/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading 7 | 28 | 29 | 30 |
31 |
32 | loading... 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/windows-titlebar/renderer/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Addon Demo 7 | 8 | 9 |

Titlebar Addon Demo

10 |

Print handle of current window

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/windows-titlebar/renderer/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { contextBridge, ipcRenderer } = require('electron'); 4 | 5 | contextBridge.exposeInMainWorld('electron', { 6 | ipcRenderer: { 7 | send: (channel, ...args) => ipcRenderer.send(channel, ...args), 8 | }, 9 | }); 10 | 11 | window.addEventListener('DOMContentLoaded', () => { 12 | document.getElementById('switch-dark-mode').addEventListener('click', () => { 13 | ipcRenderer.send('switch-theme-mode', { 14 | dark: true, 15 | }); 16 | }); 17 | 18 | document.getElementById('switch-light-mode').addEventListener('click', () => { 19 | ipcRenderer.send('switch-theme-mode', { 20 | dark: false, 21 | }); 22 | }); 23 | }); -------------------------------------------------------------------------------- /src/windows-verify-trust/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { isLibExist, getLibPath, verifyTrust } from 'windows-verify-trust'; 4 | 5 | module.exports = (app: any) => { 6 | const targetFileName = 'wlanapi.dll'; 7 | console.log('isLibExist: %s', isLibExist(targetFileName)); 8 | console.log('getLibPath: %s', getLibPath(targetFileName)); 9 | console.log('verifyTrust: %s', verifyTrust(targetFileName)); 10 | }; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "commonjs", 5 | "lib": [ 6 | "dom", 7 | "es2021" 8 | ], 9 | "declaration": true, 10 | "declarationMap": true, 11 | "jsx": "react-jsx", 12 | "strict": true, 13 | "pretty": true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "moduleResolution": "node", 21 | "esModuleInterop": true, 22 | "allowSyntheticDefaultImports": true, 23 | "useUnknownInCatchVariables": false, 24 | "resolveJsonModule": true, 25 | "skipLibCheck": true 26 | }, 27 | "outDir": "./", 28 | "include": [ 29 | "src/**/*", 30 | ], 31 | "exclude": [ 32 | "node_modules", 33 | ] 34 | } 35 | --------------------------------------------------------------------------------