├── .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 | [](https://github.com/electron-modules/electron-modules-sample/actions/workflows/ci.yml)
6 | [](https://github.com/electron-modules/electron-modules-sample/actions/workflows/release.yml)
7 | [](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 |
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 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
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 | - 🐛 Electrom
62 | - 🚀 Electron Windows
63 | - ⭐️ Electron Updator
64 | - 🌏 Electron Webview Schedule
65 | - 🌓 Electron Windows Titlebar
66 | - 💻 Network Interface
67 | - 🛡 Windows Verify Trust
68 | - 📚 Local Storage
69 | - 📘 CrashPad
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 | -
51 | checkForUpdates
52 |
53 |
54 |
55 | -
56 | downloadUpdate
57 |
58 | progress: 0
59 |
60 | -
61 | quitAndInstall
62 |
63 |
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 |
--------------------------------------------------------------------------------