You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

581 lines
26 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. // Modules to control application life and create native browser window
  2. const {app, BrowserWindow, dialog, ipcMain, screen, session} = require('electron');
  3. app.commandLine.appendSwitch("--disable-http-cache");
  4. const {Builder, By, Key, until} = require("selenium-webdriver");
  5. const chrome = require('selenium-webdriver/chrome');
  6. const {ServiceBuilder} = require('selenium-webdriver/chrome');
  7. const {rootCertificates} = require('tls');
  8. const {exit} = require('process');
  9. const path = require('path');
  10. const fs = require('fs');
  11. const {exec, spawn} = require('child_process');
  12. const iconPath = path.join(__dirname, 'favicon.ico');
  13. const task_server = require(path.join(__dirname, 'server.js'));
  14. const util = require('util');
  15. let config = fs.readFileSync(path.join(task_server.getDir(), `config.json`), 'utf8');
  16. config = JSON.parse(config);
  17. if(config.debug){
  18. let logPath = 'info.log'
  19. let logFile = fs.createWriteStream(logPath, { flags: 'a' })
  20. console.log = function() {
  21. logFile.write(util.format.apply(null, arguments) + '\n')
  22. process.stdout.write(util.format.apply(null, arguments) + '\n')
  23. }
  24. console.error = function() {
  25. logFile.write(util.format.apply(null, arguments) + '\n')
  26. process.stderr.write(util.format.apply(null, arguments) + '\n')
  27. }
  28. }
  29. let allWindowSockets = [];
  30. let allWindowScoketNames = [];
  31. task_server.start(config.webserver_port); //start local server
  32. let server_address = `${config.webserver_address}:${config.webserver_port}`;
  33. const websocket_port = 8084; //目前只支持8084端口,写死,因为扩展里面写死了
  34. console.log("server_address: " + server_address);
  35. let driverPath = "";
  36. let chromeBinaryPath = "";
  37. let execute_path = "";
  38. console.log(process.arch);
  39. exec(`wmic os get Caption`, function(error, stdout, stderr) {
  40. if (error) {
  41. console.error(`执行的错误: ${error}`);
  42. return;
  43. }
  44. if (stdout.includes('Windows 7')) {
  45. console.log('Windows 7');
  46. let sys_arch = config.sys_arch;
  47. if (sys_arch === 'x64') {
  48. dialog.showMessageBoxSync({
  49. type: 'error',
  50. title: 'Error',
  51. message: 'Windows 7系统请下载使用x32版本的软件,不论Win 7系统为x64还是x32版本。\nFor Windows 7, please download and use the x32 version of the software, regardless of whether the Win 7 system is x64 or x32 version.',
  52. });
  53. }
  54. } else {
  55. console.log('Not Windows 7');
  56. }
  57. });
  58. if (process.platform === 'win32' && process.arch === 'ia32') {
  59. driverPath = path.join(__dirname, "chrome_win32/chromedriver_win32.exe");
  60. chromeBinaryPath = path.join(__dirname, "chrome_win32/chrome.exe");
  61. execute_path = path.join(__dirname, "chrome_win32/execute.bat");
  62. } else if (process.platform === 'win32' && process.arch === 'x64') {
  63. driverPath = path.join(__dirname, "chrome_win64/chromedriver_win64.exe");
  64. chromeBinaryPath = path.join(__dirname, "chrome_win64/chrome.exe");
  65. execute_path = path.join(__dirname, "chrome_win64/execute.bat");
  66. } else if (process.platform === 'darwin') {
  67. driverPath = path.join(__dirname, "chromedriver_mac64");
  68. chromeBinaryPath = path.join(__dirname, "chrome_mac64.app/Contents/MacOS/Google Chrome");
  69. execute_path = path.join(__dirname, "");
  70. } else if (process.platform === 'linux') {
  71. driverPath = path.join(__dirname, "chrome_linux64/chromedriver_linux64");
  72. chromeBinaryPath = path.join(__dirname, "chrome_linux64/chrome");
  73. execute_path = path.join(__dirname, "chrome_linux64/execute.sh");
  74. }
  75. console.log(driverPath, chromeBinaryPath, execute_path);
  76. let language = "en";
  77. let driver = null;
  78. let mainWindow = null;
  79. let flowchart_window = null;
  80. let current_handle = null;
  81. let old_handles = [];
  82. let handle_pairs = {};
  83. // var ffi = require('ffi-napi');
  84. // var libm = ffi.Library('libm', {
  85. // 'ceil': [ 'double', [ 'double' ] ]
  86. // });
  87. // libm.ceil(1.5); // 2
  88. // const {user32FindWindowEx,
  89. // winspoolGetDefaultPrinter,} = require('win32-api/fun');
  90. // async function testt(){
  91. // // 获取当前电脑当前用户默认打印机名
  92. // const printerName = await winspoolGetDefaultPrinter()
  93. // console.log(printerName);
  94. // }
  95. // testt();
  96. function createWindow() {
  97. // Create the browser window.
  98. mainWindow = new BrowserWindow({
  99. width: 550,
  100. height: 750,
  101. webPreferences: {
  102. preload: path.join(__dirname, 'src/js/preload.js')
  103. },
  104. icon: iconPath,
  105. // frame: false, //取消window自带的关闭最小化等
  106. resizable: false //禁止改变主窗口尺寸
  107. })
  108. // and load the index.html of the app.
  109. // mainWindow.loadFile('src/index.html');
  110. mainWindow.loadURL(server_address + '/index.html?user_data_folder=' + config.user_data_folder+"&copyright=" + config.copyright, { extraHeaders: 'pragma: no-cache\n' });
  111. // 隐藏菜单栏
  112. const {Menu} = require('electron');
  113. Menu.setApplicationMenu(null);
  114. mainWindow.on('close', function (e) {
  115. if (process.platform !== 'darwin') {
  116. app.quit();
  117. }
  118. });
  119. // mainWindow.webContents.openDevTools();
  120. // Open the DevTools.
  121. // mainWindow.webContents.openDevTools()
  122. }
  123. async function beginInvoke(msg, ws) {
  124. if (msg.type == 1) {
  125. if (msg.message.id != -1) {
  126. let url = "";
  127. if (language == "zh") {
  128. url = server_address + `/taskGrid/FlowChart_CN.html?id=${msg.message.id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
  129. } else if (language == "en") {
  130. url = server_address + `/taskGrid/FlowChart.html?id=${msg.message.id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
  131. }
  132. console.log(url);
  133. flowchart_window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
  134. }
  135. mainWindow.hide();
  136. // Prints the currently focused window bounds.
  137. // This method has to be called on macOS before changing the window's bounds, otherwise it will throw an error.
  138. // It will prompt an accessibility permission request dialog, if needed.
  139. if(process.platform != "linux" && process.platform != "darwin"){
  140. const {windowManager} = require("node-window-manager");
  141. const window = windowManager.getActiveWindow();
  142. console.log(window);
  143. windowManager.requestAccessibility();
  144. // Sets the active window's bounds.
  145. let size = screen.getPrimaryDisplay().workAreaSize
  146. let width = parseInt(size.width)
  147. let height = parseInt(size.height * 0.6)
  148. window.setBounds({x: 0, y: size.height * 0.4, height: height, width: width});
  149. }
  150. flowchart_window.show();
  151. // flowchart_window.openDevTools();
  152. } else if (msg.type == 2) {
  153. // 键盘输入事件
  154. // const robot = require("@jitsi/robotjs");
  155. let keyInfo = msg.message.keyboardStr;
  156. let handles = await driver.getAllWindowHandles();
  157. console.log("handles", handles);
  158. let exit = false;
  159. let content_handle = handle_pairs[msg.message.id];
  160. console.log(msg.message.id, content_handle);
  161. let order = [...handles.filter(handle => handle != current_handle && handle != content_handle), current_handle, content_handle]; //搜索顺序
  162. let len = order.length;
  163. while (true) {
  164. // console.log("handles");
  165. try{
  166. let iframe = msg.message.iframe;
  167. let enter = false;
  168. if (/<enter>/i.test(keyInfo)) {
  169. keyInfo = keyInfo.replace(/<enter>/gi, '');
  170. enter = true;
  171. }
  172. let h = order[len - 1];
  173. console.log("current_handle", current_handle);
  174. if(h != null && handles.includes(h)){
  175. await driver.switchTo().window(h);
  176. current_handle = h;
  177. console.log("switch to handle: ", h);
  178. }
  179. // await driver.executeScript("window.stop();");
  180. // console.log("executeScript");
  181. if(!iframe){
  182. let element = await driver.findElement(By.xpath(msg.message.xpath));
  183. console.log("Find Element at handle: ", current_handle);
  184. // 使用正则表达式匹配 '<enter>',不论大小写
  185. await element.sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
  186. if(enter){
  187. await element.sendKeys(Key.ENTER);
  188. }
  189. console.log("send key");
  190. break;
  191. } else {
  192. let iframes = await driver.findElements(By.tagName('iframe'));
  193. // 遍历所有的 iframe 并点击里面的元素
  194. for(let i = 0; i < iframes.length; i++) {
  195. let iframe = iframes[i];
  196. // 切换到 iframe
  197. await driver.switchTo().frame(iframe);
  198. // 在 iframe 中查找并点击元素
  199. let element;
  200. try {
  201. element = await driver.findElement(By.xpath(msg.message.xpath));
  202. } catch (error) {
  203. console.log('No such element found in the iframe');
  204. }
  205. if (element) {
  206. await element.sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
  207. if(enter){
  208. await element.sendKeys(Key.ENTER);
  209. }
  210. }
  211. // 完成操作后切回主文档
  212. await driver.switchTo().defaultContent();
  213. }
  214. break;
  215. }
  216. } catch (error) {
  217. console.log("len", len);
  218. len = len - 1;
  219. if (len == 0) {
  220. break;
  221. }
  222. }
  223. // .then(function (element) {
  224. // console.log("element", element, handles);
  225. // element.sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
  226. // exit = true;
  227. // }, function (error) {
  228. // console.log("error", error);
  229. // len = len - 1;
  230. // if (len == 0) {
  231. // exit = true;
  232. // }
  233. // }
  234. // );
  235. }
  236. // let handles = driver.getAllWindowHandles();
  237. // driver.switchTo().window(handles[handles.length - 1]);
  238. // driver.findElement(By.xpath(msg.message.xpath)).sendKeys(Key.HOME, Key.chord(Key.SHIFT, Key.END), keyInfo);
  239. // robot.keyTap("a", "control");
  240. // robot.keyTap("backspace");
  241. // robot.typeString(keyInfo);
  242. // robot.keyTap("shift");
  243. // robot.keyTap("shift");
  244. } else if (msg.type == 3) {
  245. try {
  246. if (msg.from == 0) {
  247. socket_flowchart.send(msg.message.pipe); //直接把消息转接
  248. let message = JSON.parse(msg.message.pipe);
  249. let type = message.type;
  250. console.log("FROM Browser: ", message);
  251. console.log("Iframe:", message.iframe);
  252. if(type.indexOf("Click")>=0){
  253. // 鼠标点击事件
  254. let iframe = message.iframe;
  255. let handles = await driver.getAllWindowHandles();
  256. console.log("handles", handles);
  257. let exit = false;
  258. let content_handle = handle_pairs[message.id];
  259. console.log(message.id, content_handle);
  260. let order = [...handles.filter(handle => handle != current_handle && handle != content_handle), current_handle, content_handle]; //搜索顺序
  261. let len = order.length;
  262. while(true) {
  263. try{
  264. let h = order[len - 1];
  265. console.log("current_handle", current_handle);
  266. if(h != null && handles.includes(h)){
  267. await driver.switchTo().window(h); //执行失败会抛出异常
  268. current_handle = h;
  269. console.log("switch to handle: ", h);
  270. }
  271. //下面是找到窗口的情况下
  272. if(!iframe){
  273. let element = await driver.findElement(By.xpath(message.xpath));
  274. await element.click();
  275. break;
  276. } else {
  277. let iframes = await driver.findElements(By.tagName('iframe'));
  278. // 遍历所有的 iframe 并点击里面的元素
  279. for(let i = 0; i < iframes.length; i++) {
  280. let iframe = iframes[i];
  281. // 切换到 iframe
  282. await driver.switchTo().frame(iframe);
  283. // 在 iframe 中查找并点击元素
  284. let element;
  285. try {
  286. element = await driver.findElement(By.xpath(message.xpath));
  287. } catch (error) {
  288. console.log('No such element found in the iframe');
  289. }
  290. if (element) {
  291. await element.click();
  292. }
  293. // 完成操作后切回主文档
  294. await driver.switchTo().defaultContent();
  295. }
  296. break;
  297. }
  298. } catch (error) {
  299. console.log("len", len); //如果没有找到元素,就切换到下一个窗口
  300. len = len - 1;
  301. if (len == 0) {
  302. break;
  303. }
  304. }
  305. }
  306. }
  307. } else {
  308. socket_window.send(msg.message.pipe);
  309. for(let i in allWindowSockets){
  310. try{
  311. allWindowSockets[i].send(msg.message.pipe);
  312. } catch {
  313. console.log("Cannot send to socket with id: ", allWindowScoketNames[i]);
  314. }
  315. }
  316. console.log("FROM Flowchart: ", JSON.parse(msg.message.pipe));
  317. }
  318. } catch (e) {
  319. console.log(e);
  320. }
  321. } else if (msg.type == 5) {
  322. let child = require('child_process').execFile;
  323. // 参数顺序: 1. task id 2. server address 3. saved_file_name 4. "remote" or "local" 5. user_data_folder
  324. // var parameters = [msg.message.id, server_address];
  325. let parameters = [];
  326. console.log(msg.message)
  327. if (msg.message.user_data_folder == null || msg.message.user_data_folder == undefined || msg.message.user_data_folder == "") {
  328. parameters = ["--id", "[" + msg.message.id + "]", "--server_address", server_address, "--user_data", 0];
  329. } else {
  330. let user_data_folder_path = path.join(task_server.getDir(), msg.message.user_data_folder);
  331. parameters = ["--id", "[" + msg.message.id + "]", "--server_address", server_address, "--user_data", 1];
  332. config.user_data_folder = msg.message.user_data_folder;
  333. config.absolute_user_data_folder = user_data_folder_path;
  334. fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
  335. }
  336. if(msg.message.mysql_config_path != "-1"){
  337. config.mysql_config_path = msg.message.mysql_config_path;
  338. }
  339. fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
  340. // child('Chrome/easyspider_executestage.exe', parameters, function(err,stdout, stderr) {
  341. // console.log(stdout);
  342. // });
  343. let spawn = require("child_process").spawn;
  344. if (process.platform != "darwin" && msg.message.execute_type == 1 && msg.message.id != -1) {
  345. let child_process = spawn(execute_path, parameters);
  346. child_process.stdout.on('data', function (data) {
  347. console.log(data.toString());
  348. });
  349. }
  350. ws.send(JSON.stringify({"config_folder": task_server.getDir() + "/", "easyspider_location": task_server.getEasySpiderLocation()}));
  351. } else if (msg.type == 6) {
  352. try{
  353. flowchart_window.openDevTools();
  354. } catch {
  355. console.log("open devtools error");
  356. }
  357. } else if (msg.type == 7) {
  358. // 获得当前页面Cookies
  359. try{
  360. let cookies = await driver.manage().getCookies();
  361. console.log("Cookies: ", cookies);
  362. let cookiesText = cookies.map(cookie => `${cookie.name}=${cookie.value}`).join('\n');
  363. socket_flowchart.send(JSON.stringify({"type": "GetCookies", "message": cookiesText}));
  364. } catch {
  365. console.log("Cannot get Cookies");
  366. }
  367. }
  368. }
  369. const WebSocket = require('ws');
  370. const {all} = require("express/lib/application");
  371. let socket_window = null;
  372. let socket_start = null;
  373. let socket_flowchart = null;
  374. let wss = new WebSocket.Server({port: websocket_port});
  375. wss.on('connection', function (ws) {
  376. ws.on('message', async function (message, isBinary) {
  377. let msg = JSON.parse(message.toString());
  378. console.log("\n\nGET A MESSAGE: ", msg);
  379. // console.log(msg, msg.type, msg.message);
  380. if (msg.type == 0) {
  381. if (msg.message.id == 0) {
  382. socket_window = ws;
  383. console.log("set socket_window")
  384. } else if (msg.message.id == 1) {
  385. socket_start = ws;
  386. console.log("set socket_start")
  387. } else if (msg.message.id == 2) {
  388. socket_flowchart = ws;
  389. console.log("set socket_flowchart");
  390. } else { //其他的ID是用来标识不同的浏览器标签页的
  391. await new Promise(resolve => setTimeout(resolve, 2300));
  392. let handles = await driver.getAllWindowHandles();
  393. if(arrayDifference(handles, old_handles).length > 0){
  394. old_handles = handles;
  395. current_handle = handles[handles.length - 1];
  396. console.log("New tab opened, change current_handle to: ", current_handle);
  397. }
  398. handle_pairs[msg.message.id] = current_handle;
  399. console.log("Set handle_pair for id: ", msg.message.id, " to ", current_handle, ", title is: ", msg.message.title);
  400. socket_flowchart.send(JSON.stringify({"type": "title", "data": {"title":msg.message.title}}));
  401. allWindowSockets.push(ws);
  402. allWindowScoketNames.push(msg.message.id);
  403. // console.log("handle_pairs: ", handle_pairs);
  404. }
  405. } else if (msg.type == 10) {
  406. let leave_handle = handle_pairs[msg.message.id];
  407. if (leave_handle!=null && leave_handle!=undefined && leave_handle!="")
  408. {
  409. await driver.switchTo().window(leave_handle);
  410. console.log("Switch to handle: ", leave_handle);
  411. current_handle = leave_handle;
  412. }
  413. }
  414. else {
  415. await beginInvoke(msg, ws);
  416. }
  417. });
  418. });
  419. console.log(process.platform);
  420. async function runBrowser(lang = "en", user_data_folder = '', mobile = false) {
  421. const serviceBuilder = new ServiceBuilder(driverPath);
  422. let options = new chrome.Options();
  423. options.addArguments('--disable-blink-features=AutomationControlled');
  424. language = lang;
  425. if (lang == "en") {
  426. options.addExtensions(path.join(__dirname, "EasySpider_en.crx"));
  427. } else if (lang == "zh") {
  428. options.addExtensions(path.join(__dirname, "EasySpider_zh.crx"));
  429. }
  430. options.addExtensions(path.join(__dirname, "XPathHelper.crx"));
  431. options.setChromeBinaryPath(chromeBinaryPath);
  432. if (user_data_folder != "") {
  433. let dir = path.join(task_server.getDir(), user_data_folder);
  434. console.log(dir);
  435. options.addArguments("--user-data-dir=" + dir);
  436. config.user_data_folder = user_data_folder;
  437. fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
  438. }
  439. if (mobile) {
  440. const mobileEmulation = {
  441. deviceName: 'iPhone XR'
  442. };
  443. options.addArguments(`--user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 13_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"`);
  444. options.setMobileEmulation(mobileEmulation);
  445. }
  446. driver = new Builder()
  447. .forBrowser('chrome')
  448. .setChromeOptions(options)
  449. .setChromeService(serviceBuilder)
  450. .build();
  451. await driver.manage().setTimeouts({implicit: 10000, pageLoad: 10000, script: 10000});
  452. await driver.executeScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
  453. // await driver.executeScript("localStorage.clear();"); //重置参数数量
  454. const cdpConnection = await driver.createCDPConnection("page");
  455. let stealth_path = path.join(__dirname, "stealth.min.js");
  456. let stealth = fs.readFileSync(stealth_path, 'utf8');
  457. await cdpConnection.execute('Page.addScriptToEvaluateOnNewDocument', {
  458. source: stealth,
  459. });
  460. try {
  461. if(mobile){
  462. await driver.get(server_address + "/taskGrid/taskList.html?wsport=" + websocket_port + "&backEndAddressServiceWrapper=" + server_address + "&mobile=1&lang=" + lang);
  463. } else {
  464. await driver.get(server_address + "/taskGrid/taskList.html?wsport=" + websocket_port + "&backEndAddressServiceWrapper=" + server_address + "&lang=" + lang);
  465. }
  466. old_handles = await driver.getAllWindowHandles();
  467. current_handle = old_handles[old_handles.length - 1];
  468. } finally {
  469. // await driver.quit(); // 退出浏览器
  470. }
  471. }
  472. function handleOpenBrowser(event, lang = "en", user_data_folder = "", mobile = false) {
  473. const webContents = event.sender;
  474. const win = BrowserWindow.fromWebContents(webContents);
  475. runBrowser(lang, user_data_folder, mobile);
  476. let size = screen.getPrimaryDisplay().workAreaSize;
  477. let width = parseInt(size.width);
  478. let height = parseInt(size.height * 0.6);
  479. flowchart_window = new BrowserWindow({
  480. x: 0,
  481. y: 0,
  482. width: width,
  483. height: height,
  484. icon: iconPath,
  485. });
  486. let url = "";
  487. let id = -1;
  488. if (lang == "en") {
  489. url = server_address + `/taskGrid/FlowChart.html?id=${id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address + "&mobile=" + mobile.toString();
  490. } else if (lang == "zh") {
  491. url = server_address + `/taskGrid/FlowChart_CN.html?id=${id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address+ "&mobile=" + mobile.toString();
  492. }
  493. // and load the index.html of the app.
  494. flowchart_window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
  495. if(process.platform != "darwin"){
  496. flowchart_window.hide();
  497. }
  498. flowchart_window.on('close', function (event) {
  499. mainWindow.show();
  500. driver.quit();
  501. });
  502. }
  503. function handleOpenInvoke(event, lang = "en") {
  504. const window = new BrowserWindow({icon: iconPath});
  505. let url = "";
  506. language = lang;
  507. if (lang == "en") {
  508. url = server_address + `/taskGrid/taskList.html?type=1&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
  509. } else if (lang == "zh") {
  510. url = server_address + `/taskGrid/taskList.html?type=1&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address + "&lang=zh";
  511. }
  512. // and load the index.html of the app.
  513. window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
  514. window.maximize();
  515. mainWindow.hide();
  516. window.on('close', function (event) {
  517. mainWindow.show();
  518. });
  519. }
  520. // This method will be called when Electron has finished
  521. // initialization and is ready to create browser windows.
  522. // Some APIs can only be used after this event occurs.
  523. app.whenReady().then(() => {
  524. session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
  525. details.requestHeaders['Accept-Language'] = 'zh'
  526. callback({ cancel: false, requestHeaders: details.requestHeaders })
  527. })
  528. ipcMain.on('start-design', handleOpenBrowser);
  529. ipcMain.on('start-invoke', handleOpenInvoke);
  530. ipcMain.on('accept-agreement', function (event, arg) {
  531. config.copyright = 1;
  532. fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
  533. });
  534. createWindow();
  535. app.on('activate', function () {
  536. // On macOS it's common to re-create a window in the app when the
  537. // dock icon is clicked and there are no other windows open.
  538. if (BrowserWindow.getAllWindows().length === 0) {
  539. createWindow();
  540. }
  541. })
  542. })
  543. // Quit when all windows are closed, except on macOS. There, it's common
  544. // for applications and their menu bar to stay active until the user quits
  545. // explicitly with Cmd + Q.
  546. app.on('window-all-closed', function () {
  547. if (process.platform !== 'darwin') {
  548. app.quit();
  549. }
  550. })
  551. // In this file you can include the rest of your app's specific main process
  552. // code. You can also put them in separate files and require them here.
  553. function arrayDifference(arr1, arr2) {
  554. return arr1.filter(item => !arr2.includes(item));
  555. }