diff --git a/electron/cert.js b/electron/cert.js
new file mode 100644
index 0000000..b0920a5
--- /dev/null
+++ b/electron/cert.js
@@ -0,0 +1,42 @@
+import CONFIG from './const';
+import mkdirp from 'mkdirp';
+import fs from 'fs';
+import path from 'path';
+import sudo from 'sudo-prompt';
+import { clipboard, dialog } from 'electron';
+
+function checkCertInstalled() {
+ return fs.existsSync(CONFIG.INSTALL_CERT_FLAG);
+}
+
+export async function installCert(checkInstalled = true) {
+ if (checkInstalled && checkCertInstalled()) {
+ return;
+ }
+
+ if (process.platform === 'darwin') {
+ return new Promise((resolve, reject) => {
+ mkdirp.sync(path.dirname(CONFIG.INSTALL_CERT_FLAG));
+ clipboard.writeText(
+ `echo "输入本地登录密码" && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${CONFIG.CERT_PUBLIC_PATH}" && touch ${CONFIG.INSTALL_CERT_FLAG} && echo "安装完成"`,
+ );
+ dialog.showMessageBoxSync({
+ type: 'info',
+ message: `命令已复制到剪贴板,粘贴命令到终端并运行以安装并信任证书`,
+ });
+
+ reject();
+ });
+ } else {
+ return sudo.exec(
+ `${CONFIG.WIN_CERT_INSTALL_HELPER} -c -add ${CONFIG.CERT_PUBLIC_PATH} -s root`,
+ { name: CONFIG.APP_EN_NAME },
+ (error, stdout) => {
+ if (error) {
+ reject(error);
+ }
+ resolve(stdout);
+ },
+ );
+ }
+}
diff --git a/electron/const.js b/electron/const.js
index de9f7a3..afe473f 100644
--- a/electron/const.js
+++ b/electron/const.js
@@ -1,24 +1,34 @@
-import path from "path";
-import isDev from "electron-is-dev";
-import url from "url";
-import { app } from "electron";
+import path from 'path';
+import isDev from 'electron-is-dev';
+import url from 'url';
+import os from 'os';
+import { app } from 'electron';
const APP_PATH = app.getAppPath();
// 对于一些 shell 去执行的文件,asar 目录下无法使用。配合 extraResources
const EXECUTABLE_PATH = path.join(
- APP_PATH.indexOf("app.asar") > -1
- ? APP_PATH.substring(0, APP_PATH.indexOf("app.asar"))
+ APP_PATH.indexOf('app.asar') > -1
+ ? APP_PATH.substring(0, APP_PATH.indexOf('app.asar'))
: APP_PATH,
- "public"
+ 'public',
);
+const HOME_PATH = path.join(os.homedir(), '.wechat-video-downloader');
export default {
APP_START_URL: isDev
- ? "http://localhost:3000"
+ ? 'http://localhost:3000'
: url.format({
- pathname: path.join(APP_PATH, "./build/index.html"),
- protocol: "file:",
+ pathname: path.join(APP_PATH, './build/index.html'),
+ protocol: 'file:',
slashes: true,
}),
IS_DEV: isDev,
+ EXECUTABLE_PATH,
+ HOME_PATH,
+ CERT_PRIVATE_PATH: path.join(EXECUTABLE_PATH, './keys/private.pem'),
+ CERT_PUBLIC_PATH: path.join(EXECUTABLE_PATH, './keys/public.pem'),
+ INSTALL_CERT_FLAG: path.join(HOME_PATH, './installed.lock'),
+ WIN_CERT_INSTALL_HELPER: path.join(EXECUTABLE_PATH, './w_c.exe'),
+ APP_CN_NAME: '微信视频号下载器',
+ APP_EN_NAME: 'WeChat Video Downloader',
};
diff --git a/electron/index.js b/electron/index.js
index a02ea5a..8e38b8c 100644
--- a/electron/index.js
+++ b/electron/index.js
@@ -1,18 +1,20 @@
-import { app, BrowserWindow } from "electron";
-import log from "electron-log";
-import CONFIG from "./const";
-import { checkUpdate } from "./utils";
+import { app, BrowserWindow } from 'electron';
+import log from 'electron-log';
+import CONFIG from './const';
+import { checkUpdate } from './utils';
+import { startServer } from './proxyServer';
+import { installCert } from './cert';
-app.commandLine.appendSwitch("--no-proxy-server");
+app.commandLine.appendSwitch('--no-proxy-server');
function createWindow() {
// electron.Menu.setApplicationMenu(null);
checkUpdate(
- "https://cdn.jsdelivr.net/gh/lecepin/electron-react-tpl/package.json",
- "https://github.com/lecepin/electron-react-tpl/releases"
+ 'https://cdn.jsdelivr.net/gh/lecepin/electron-react-tpl/package.json',
+ 'https://github.com/lecepin/electron-react-tpl/releases',
);
- mainWindow = new BrowserWindow({
+ const mainWindow = new BrowserWindow({
width: 800,
height: 600,
// resizable: false,
@@ -24,26 +26,29 @@ function createWindow() {
},
});
- mainWindow.loadURL(CONFIG.APP_START_URL);
+ mainWindow.loadURL('https://baidu.com' ?? CONFIG.APP_START_URL);
CONFIG.IS_DEV && mainWindow.webContents.openDevTools();
-
- mainWindow.on("closed", function () {
- mainWindow = null;
- });
+ installCert()
+ .then(data => {
+ console.log('install cert success', data);
+ })
+ .catch(err => {
+ log.error('err', err);
+ });
}
app.whenReady().then(() => {
createWindow();
- app.on("activate", () => {
+ app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
-app.on("window-all-closed", () => {
- if (process.platform !== "darwin") {
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') {
app.quit();
}
});
diff --git a/electron/proxyServer.js b/electron/proxyServer.js
new file mode 100644
index 0000000..3752ee3
--- /dev/null
+++ b/electron/proxyServer.js
@@ -0,0 +1,37 @@
+import path from 'path';
+import fs from 'fs';
+import hoxy from 'hoxy';
+import getPort from 'get-port';
+import { app } from 'electron';
+import CONFIG from './const';
+import { setProxy, closeProxy } from './setProxy';
+
+export async function startServer({ interceptCallback = f => f, errorCallback = f => f }) {
+ const port = await getPort();
+ const proxy = hoxy
+ .createServer({
+ certAuthority: {
+ key: fs.readFileSync(CONFIG.CERT_PRIVATE_PATH),
+ cert: fs.readFileSync(CONFIG.CERT_PUBLIC_PATH),
+ },
+ })
+ .listen(port, () => {
+ setProxy('127.0.0.1', port).catch(errorCallback);
+ });
+
+ proxy.intercept(
+ {
+ phase: 'request',
+ },
+ interceptCallback,
+ );
+}
+
+app.on('before-quit', async e => {
+ e.preventDefault();
+ try {
+ await closeProxy();
+ } catch (error) {}
+
+ app.exit();
+});
diff --git a/electron/setProxy.js b/electron/setProxy.js
new file mode 100644
index 0000000..4cad461
--- /dev/null
+++ b/electron/setProxy.js
@@ -0,0 +1,118 @@
+import { exec } from 'child_process';
+import regedit from 'regedit';
+
+export async function setProxy(host, port) {
+ if (process.platform === 'darwin') {
+ const networks = await getMacAvailableNetworks();
+
+ if (networks.length === 0) {
+ throw 'no network';
+ }
+
+ return Promise.all(
+ networks.map(network => {
+ return new Promise((resolve, reject) => {
+ exec(`networksetup -setsecurewebproxy "${network}" ${host} ${port}`, error => {
+ if (error) {
+ reject(null);
+ } else {
+ resolve(network);
+ }
+ });
+ });
+ }),
+ );
+ } else {
+ const valuesToPut = {
+ 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings': {
+ ProxyServer: {
+ value: `${host}:${port}`,
+ type: 'REG_SZ',
+ },
+ ProxyEnable: {
+ value: 1,
+ type: 'REG_DWORD',
+ },
+ },
+ };
+ return editWinRegPromise(valuesToPut);
+ }
+}
+
+export async function closeProxy() {
+ if (process.platform === 'darwin') {
+ const networks = await getMacAvailableNetworks();
+
+ if (networks.length === 0) {
+ throw 'no network';
+ }
+
+ return Promise.all(
+ networks.map(network => {
+ return new Promise((resolve, reject) => {
+ exec(`networksetup -setsecurewebproxystate "${network}" off`, error => {
+ if (error) {
+ reject(null);
+ } else {
+ resolve(network);
+ }
+ });
+ });
+ }),
+ );
+ } else {
+ const valuesToPut = {
+ 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings': {
+ ProxyEnable: {
+ value: 0,
+ type: 'REG_DWORD',
+ },
+ },
+ };
+ return editWinRegPromise(valuesToPut);
+ }
+}
+
+function getMacAvailableNetworks() {
+ return new Promise((resolve, reject) => {
+ exec('networksetup -listallnetworkservices', (error, stdout) => {
+ if (error) {
+ reject(error);
+ } else {
+ Promise.all(
+ stdout
+ .toString()
+ .split('\n')
+ .map(network => {
+ return new Promise(resolve => {
+ exec(
+ `networksetup getinfo "${network}" | grep "^IP address:\\s\\d"`,
+ (error, stdout) => {
+ if (error) {
+ resolve(null);
+ } else {
+ resolve(stdout ? network : null);
+ }
+ },
+ );
+ });
+ }),
+ ).then(networks => {
+ resolve(networks.filter(Boolean));
+ });
+ }
+ });
+ });
+}
+
+function editWinRegPromise(valuesToPut) {
+ return new Promise((resolve, reject) => {
+ regedit.putValue(valuesToPut, function (err) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
diff --git a/electron/utils.js b/electron/utils.js
index c0fd25c..61e9648 100644
--- a/electron/utils.js
+++ b/electron/utils.js
@@ -1,22 +1,22 @@
-import { get } from "axios";
-const { app, dialog, shell } = require("electron");
-import semver from "semver";
+import { get } from 'axios';
+const { app, dialog, shell } = require('electron');
+import semver from 'semver';
// packageUrl 需要包含 { "version": "1.0.0" } 结构
function checkUpdate(
// 可以使用加速地址 https://cdn.jsdelivr.net/gh/lecepin/electron-react-tpl/package.json
- packageUrl = "https://raw.githubusercontent.com/lecepin/electron-react-tpl/master/package.json",
- downloadUrl = "https://github.com/lecepin/electron-react-tpl/releases"
+ packageUrl = 'https://raw.githubusercontent.com/lecepin/electron-react-tpl/master/package.json',
+ downloadUrl = 'https://github.com/lecepin/electron-react-tpl/releases',
) {
get(packageUrl)
.then(({ data }) => {
if (semver.gt(data?.version, app.getVersion())) {
const result = dialog.showMessageBoxSync({
- message: "发现新版本,是否更新?",
- type: "question",
+ message: '发现新版本,是否更新?',
+ type: 'question',
cancelId: 1,
defaultId: 0,
- buttons: ["进入新版本下载页面", "取消"],
+ buttons: ['进入新版本下载页面', '取消'],
});
if (result === 0 && downloadUrl) {
@@ -24,7 +24,7 @@ function checkUpdate(
}
}
})
- .catch((err) => {});
+ .catch(err => {});
}
export { checkUpdate };
diff --git a/package.json b/package.json
index 748a543..5accc21 100644
--- a/package.json
+++ b/package.json
@@ -8,12 +8,13 @@
"postinstall": "husky install",
"start": "concurrently \"cross-env BROWSER=none npm run start-web\" \"wait-on http://localhost:3000 && npm run start-electron\" ",
"start-web": "react-app-rewired start",
- "start-electron": "parcel build --target electron --no-cache && electron .",
+ "start-electron": "webpack --config webpack.electron.js && electron .",
"build-web": "react-app-rewired build",
- "build-electron": "parcel build --target electron --no-cache",
+ "build-electron": "webpack --config webpack.electron.js",
"build-all": "rm -rf ./build && rm -rf ./build-electron && npm run build-electron && npm run build-web",
"pack": "npm run build-all && electron-builder",
- "gen-icon": "electron-icon-builder --input=./public/icon/icon.png --output=./public/icon"
+ "gen-icon": "electron-icon-builder --input=./public/icon/icon.png --output=./public/icon",
+ "pretty": "prettier -c --write \"(src/**/*|electron/**/*)\""
},
"targets": {
"electron": {
@@ -41,15 +42,22 @@
"prettier": "^2.6.2",
"react-app-rewired": "^2.2.1",
"react-scripts": "5.0.1",
- "wait-on": "^6.0.1"
+ "wait-on": "^6.0.1",
+ "webpack": "^5.72.1",
+ "webpack-cli": "^4.9.2"
},
"dependencies": {
"axios": "^0.27.2",
"electron-is-dev": "^2.0.0",
"electron-log": "^4.4.7",
+ "get-port": "^6.1.2",
+ "hoxy": "^3.3.1",
+ "mkdirp": "^1.0.4",
"react": "^18.1.0",
"react-dom": "^18.1.0",
- "semver": "^7.3.7"
+ "regedit": "^5.1.1",
+ "semver": "^7.3.7",
+ "sudo-prompt": "^9.2.1"
},
"author": "lecepin",
"license": "ISC",
diff --git a/public/w_c.exe b/public/w_c.exe
new file mode 100644
index 0000000..8607884
Binary files /dev/null and b/public/w_c.exe differ
diff --git a/src/App.jsx b/src/App.jsx
index 22f5052..c74eb8d 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,6 +1,6 @@
-import logo from "./logo.png";
-import { shell } from "electron";
-import "./App.css";
+import logo from './logo.png';
+import { shell } from 'electron';
+import './App.css';
function App() {
return (
@@ -11,9 +11,9 @@ function App() {
Edit src/App.js
and save to reload.