server: 平台抽象层+bilibili、服务端加密凭据库、角色中间件;web: 精简页面/路由、macOS 客户端(Swift)与多份方案文档;移除误入库的编译产物
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
// 生成 icons/app.png(256x256 纯蓝方块图标)
|
||||
// 生成 icons/app.png(512x512 纯蓝方块图标,兼容 macOS DMG)
|
||||
const zlib = require("zlib");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const W = 256, H = 256;
|
||||
const W = 512, H = 512;
|
||||
const row = Buffer.alloc(1 + W * 4);
|
||||
row[0] = 0;
|
||||
for (let x = 0; x < W; x++) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 938 B |
Binary file not shown.
|
Before Width: | Height: | Size: 916 B |
+30
-7
@@ -1,4 +1,4 @@
|
||||
const { app, BrowserWindow, Tray, Menu, ipcMain, shell, nativeImage } = require('electron');
|
||||
const { app, BrowserWindow, Tray, Menu, Notification, ipcMain, shell, nativeImage } = require('electron');
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
@@ -17,6 +17,7 @@ let localJsonTimer = null;
|
||||
let quitting = false;
|
||||
|
||||
const dataDir = path.join(app.getPath('userData'), 'data');
|
||||
const localJsonPath = path.join(dataDir, 'local.json');
|
||||
const coreBin = isWin ? 'agent-core.exe' : 'agent-core';
|
||||
const coreExe = app.isPackaged
|
||||
? path.join(process.resourcesPath, coreBin)
|
||||
@@ -30,7 +31,14 @@ function startCore() {
|
||||
}
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
try { coreProc?.kill(); } catch (e) { /* ignore */ }
|
||||
corePort = 0;
|
||||
coreToken = '';
|
||||
try { fs.rmSync(localJsonPath, { force: true }); } catch (e) { /* ignore */ }
|
||||
coreProc = spawn(coreExe, ['-dir', dataDir], { windowsHide: true });
|
||||
coreProc.on('error', (err) => {
|
||||
console.error('[core] process error: ' + err.message);
|
||||
win?.webContents.send('core-error', '核心程序启动失败:' + err.message);
|
||||
});
|
||||
coreProc.on('exit', (code) => {
|
||||
coreProc = null;
|
||||
win?.webContents.send('core-exit', code);
|
||||
@@ -42,7 +50,7 @@ function pollLocalJson() {
|
||||
if (localJsonTimer) clearInterval(localJsonTimer);
|
||||
localJsonTimer = setInterval(() => {
|
||||
try {
|
||||
const info = JSON.parse(fs.readFileSync(path.join(dataDir, 'local.json'), 'utf8'));
|
||||
const info = JSON.parse(fs.readFileSync(localJsonPath, 'utf8'));
|
||||
if (info.port && info.token) {
|
||||
corePort = info.port;
|
||||
coreToken = info.token;
|
||||
@@ -91,20 +99,31 @@ ipcMain.handle('core:save-config', (e, cfg) => { writeConfig(cfg); startCore();
|
||||
ipcMain.handle('core:restart', () => { startCore(); return true; });
|
||||
ipcMain.handle('app:open-data-dir', () => shell.openPath(dataDir));
|
||||
ipcMain.handle('app:open-web', (e, url) => shell.openExternal(url));
|
||||
ipcMain.handle('app:notify', (e, title, body) => {
|
||||
if (Notification.isSupported()) {
|
||||
new Notification({ title: String(title || 'EveryPublish'), body: String(body || '') }).show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle('app:set-autostart', (e, on) => {
|
||||
app.setLoginItemSettings({ openAtLogin: !!on });
|
||||
app.setLoginItemSettings({ openAtLogin: !!on, openAsHidden: true, args: ['--minimized'] });
|
||||
return app.getLoginItemSettings().openAtLogin;
|
||||
});
|
||||
ipcMain.handle('app:get-autostart', () => app.getLoginItemSettings().openAtLogin);
|
||||
ipcMain.handle('app:quit', () => { quitting = true; app.quit(); });
|
||||
|
||||
function readConfig() {
|
||||
try { return JSON.parse(fs.readFileSync(path.join(dataDir, 'config.json'), 'utf8')); }
|
||||
catch (e) { return { server: '', fakeAll: false }; }
|
||||
const defaults = {
|
||||
server: 'http://127.0.0.1:8090',
|
||||
webUrl: 'http://127.0.0.1:3003',
|
||||
fakeAll: false,
|
||||
};
|
||||
try { return { ...defaults, ...JSON.parse(fs.readFileSync(path.join(dataDir, 'config.json'), 'utf8')) }; }
|
||||
catch (e) { return defaults; }
|
||||
}
|
||||
function writeConfig(cfg) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dataDir, 'config.json'), JSON.stringify(cfg, null, 2));
|
||||
fs.writeFileSync(path.join(dataDir, 'config.json'), JSON.stringify({ ...readConfig(), ...cfg }, null, 2), { mode: 0o600 });
|
||||
}
|
||||
|
||||
function createTray() {
|
||||
@@ -172,6 +191,10 @@ async function runSmoke() {
|
||||
}
|
||||
}
|
||||
|
||||
app.on('before-quit', () => { quitting = true; try { coreProc?.kill(); } catch (e) { /* ignore */ } });
|
||||
app.on('before-quit', () => {
|
||||
quitting = true;
|
||||
if (localJsonTimer) clearInterval(localJsonTimer);
|
||||
try { coreProc?.kill(); } catch (e) { /* ignore */ }
|
||||
});
|
||||
app.on('window-all-closed', () => { /* 驻留托盘不退出 */ });
|
||||
app.on('activate', showWin);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "everypublish-client",
|
||||
"version": "0.2.0",
|
||||
"description": "EveryPublish Windows 客户端(Electron 壳 + Go agent-core)",
|
||||
"description": "EveryPublish 历史 Electron 调试壳(非 macOS 正式交付)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
@@ -17,12 +17,12 @@
|
||||
"productName": "EveryPublish",
|
||||
"directories": { "output": "release" },
|
||||
"files": ["main.js", "preload.js", "renderer/**/*", "icons/**/*"],
|
||||
"extraResources": [
|
||||
{ "from": "../dist/agent-core.exe", "to": "agent-core.exe" }
|
||||
],
|
||||
"win": {
|
||||
"target": [{ "target": "nsis", "arch": ["x64"] }],
|
||||
"icon": "icons/app.ico",
|
||||
"extraResources": [
|
||||
{ "from": "../dist/agent-core.exe", "to": "agent-core.exe" }
|
||||
],
|
||||
"signAndEditExecutable": false
|
||||
},
|
||||
"nsis": {
|
||||
|
||||
@@ -10,6 +10,7 @@ contextBridge.exposeInMainWorld('api', {
|
||||
restart: () => ipcRenderer.invoke('core:restart'),
|
||||
openDataDir: () => ipcRenderer.invoke('app:open-data-dir'),
|
||||
openWeb: (url) => ipcRenderer.invoke('app:open-web', url),
|
||||
notify: (title, body) => ipcRenderer.invoke('app:notify', title, body),
|
||||
setAutoStart: (on) => ipcRenderer.invoke('app:set-autostart', on),
|
||||
getAutoStart: () => ipcRenderer.invoke('app:get-autostart'),
|
||||
quit: () => ipcRenderer.invoke('app:quit'),
|
||||
|
||||
@@ -13,6 +13,7 @@ document.querySelectorAll('.tab').forEach((tab) => {
|
||||
|
||||
// ---- 状态页 ----
|
||||
let currentServer = '';
|
||||
let currentWeb = 'http://127.0.0.1:3003';
|
||||
async function refreshStatus() {
|
||||
try {
|
||||
const s = await window.api.status();
|
||||
@@ -33,7 +34,7 @@ setInterval(refreshStatus, 3000);
|
||||
refreshStatus();
|
||||
|
||||
$('btn-open-web').addEventListener('click', () => {
|
||||
const url = currentServer || 'http://127.0.0.1:8090';
|
||||
const url = currentWeb || currentServer || 'http://127.0.0.1:3003';
|
||||
window.api.openWeb(url);
|
||||
});
|
||||
$('btn-open-data').addEventListener('click', () => window.api.openDataDir());
|
||||
@@ -66,35 +67,42 @@ $('btn-pair').addEventListener('click', async () => {
|
||||
// ---- 挑战页 ----
|
||||
let selectedChallenge = null;
|
||||
let pollTimer = null;
|
||||
let knownChallengeIds = new Set();
|
||||
const KIND_LABEL = { qr: '扫码', sms: '短信验证码', confirm: 'APP 确认', captcha: '图形验证码', pending: '等待设备' };
|
||||
|
||||
async function refreshChallenges() {
|
||||
try {
|
||||
const r = await window.api.challenges();
|
||||
const list = (r && r.list) || [];
|
||||
const fresh = list.filter((ch) => ch.challengeId && !knownChallengeIds.has(ch.challengeId));
|
||||
if (fresh.length && knownChallengeIds.size > 0 && window.api.notify) {
|
||||
window.api.notify('EveryPublish 需要处理挑战', fresh.map((ch) => ch.platform || '平台').join('、') + ' 有新的扫码或验证码任务');
|
||||
}
|
||||
knownChallengeIds = new Set(list.map((ch) => ch.challengeId).filter(Boolean));
|
||||
const badge = $('ch-badge');
|
||||
if (list.length > 0) { badge.textContent = list.length; badge.classList.remove('hidden'); }
|
||||
else badge.classList.add('hidden');
|
||||
const box = $('ch-list');
|
||||
box.innerHTML = '';
|
||||
box.replaceChildren();
|
||||
list.forEach((ch) => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'item' + (selectedChallenge && selectedChallenge.challengeId === ch.challengeId ? ' selected' : '');
|
||||
const inner = [
|
||||
'<span><b>',
|
||||
ch.platform || '?',
|
||||
'</b> · ',
|
||||
KIND_LABEL[ch.kind] || ch.kind,
|
||||
'</span><span class="p">',
|
||||
ch.prompt || '',
|
||||
'</span>',
|
||||
].join('');
|
||||
div.innerHTML = inner;
|
||||
const label = document.createElement('span');
|
||||
const platform = document.createElement('b');
|
||||
platform.textContent = ch.platform || '?';
|
||||
label.append(platform, document.createTextNode(' · ' + (KIND_LABEL[ch.kind] || ch.kind)));
|
||||
const prompt = document.createElement('span');
|
||||
prompt.className = 'p';
|
||||
prompt.textContent = ch.prompt || '';
|
||||
div.append(label, prompt);
|
||||
div.addEventListener('click', () => selectChallenge(ch));
|
||||
box.appendChild(div);
|
||||
});
|
||||
if (!list.length) {
|
||||
box.innerHTML = '<p class="hint">暂无待处理挑战</p>';
|
||||
const empty = document.createElement('p');
|
||||
empty.className = 'hint';
|
||||
empty.textContent = '暂无待处理挑战';
|
||||
box.appendChild(empty);
|
||||
}
|
||||
} catch (e) { /* 忽略 */ }
|
||||
}
|
||||
@@ -149,7 +157,9 @@ startPolling();
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const cfg = await window.api.getConfig();
|
||||
$('set-server').value = (cfg && cfg.server) || '';
|
||||
$('set-server').value = (cfg && cfg.server) || 'http://127.0.0.1:8090';
|
||||
$('set-web').value = (cfg && cfg.webUrl) || 'http://127.0.0.1:3003';
|
||||
currentWeb = $('set-web').value;
|
||||
$('set-fake').checked = !!(cfg && cfg.fakeAll);
|
||||
$('set-autostart').checked = await window.api.getAutoStart();
|
||||
} catch (e) { /* 忽略 */ }
|
||||
@@ -158,12 +168,15 @@ loadSettings();
|
||||
|
||||
$('btn-save').addEventListener('click', async () => {
|
||||
const server = $('set-server').value.trim();
|
||||
const webUrl = $('set-web').value.trim();
|
||||
const res = $('set-result');
|
||||
if (!server) { res.textContent = '请填写服务器地址(内网部署填 http://服务器IP:8090)'; res.className = 'msg err'; return; }
|
||||
const cfg = { server: server, fakeAll: $('set-fake').checked };
|
||||
if (!webUrl) { res.textContent = '请填写网站地址'; res.className = 'msg err'; return; }
|
||||
const cfg = { server: server, webUrl: webUrl, fakeAll: $('set-fake').checked };
|
||||
await window.api.saveConfig(cfg);
|
||||
await window.api.setAutoStart($('set-autostart').checked);
|
||||
currentServer = server;
|
||||
currentWeb = webUrl;
|
||||
res.textContent = '已保存,核心进程已重启';
|
||||
res.className = 'msg ok';
|
||||
});
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
<h2>设置</h2>
|
||||
<label>服务器地址(内网部署时填 http://服务器IP:8090)</label>
|
||||
<input id="set-server" placeholder="http://192.168.x.x:8090" />
|
||||
<label>网站地址(本地开发默认 http://127.0.0.1:3003)</label>
|
||||
<input id="set-web" placeholder="http://127.0.0.1:3003" />
|
||||
<label class="switch-row">
|
||||
<input type="checkbox" id="set-fake" />
|
||||
<span>模拟执行(无真实账号的安装联调)</span>
|
||||
|
||||
Reference in New Issue
Block a user