webview.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. /* Copyright (C) 2024 Richard Hao Cao
  2. Ebrowser is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  3. Ebrowser is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  4. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
  5. */
  6. const {
  7. app, BrowserWindow, Menu, shell, clipboard,
  8. session, protocol, dialog, ipcMain
  9. } = require('electron')
  10. let win;
  11. if(!app.requestSingleInstanceLock())
  12. app.quit()
  13. else {
  14. app.on('ready', createWindow);
  15. app.on('second-instance', (event, args, cwd) => {
  16. if (win) {
  17. if (win.isMinimized()) {
  18. win.restore()
  19. }
  20. win.show()
  21. win.focus()
  22. cmdlineProcess(args,cwd,1);
  23. }else
  24. createWindow();
  25. })
  26. }
  27. Menu.setApplicationMenu(null);
  28. const fs = require('fs');
  29. const path = require('path')
  30. const https = require('https');
  31. const url = require('url');
  32. var translateRes;
  33. {
  34. let langs = app.getPreferredSystemLanguages();
  35. if(langs.length==0 || langs[0].startsWith('en'))
  36. topMenu();
  37. else
  38. initTranslateRes(langs[0]);
  39. }
  40. var repositoryurl = "https://gitlab.com/jamesfengcao/uweb/-/raw/master/misc/ebrowser/";
  41. const readline = require('readline');
  42. const process = require('process')
  43. var gredirects = [];
  44. var gredirect;
  45. var redirects;
  46. var bRedirect = true;
  47. var bJS = true;
  48. var bForwardCookie = true;
  49. var proxies = {};
  50. var proxy;
  51. var useragents = {};
  52. var downloadMenus; //[]
  53. var selectMenus = [];
  54. var defaultUA;
  55. {
  56. let sys = "X11; Linux x86_64";
  57. if (process.platform === "win32")
  58. sys = "Window NT 10.0; Win64; x64";
  59. else if (process.platform === "darwin")
  60. sys = "Macintosh; Intel Mac OS X 10_15_7";
  61. defaultUA =
  62. `Mozilla/5.0 (${sys}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/` +
  63. process.versions.chrome +" Safari/537.36";
  64. }
  65. app.userAgentFallback = defaultUA;
  66. fs.readFile(path.join(__dirname,'redirect.json'), 'utf8', (err, jsonString) => {
  67. if (err) return;
  68. try {
  69. redirects = JSON.parse(jsonString);
  70. } catch (e){console.log(e)}
  71. });
  72. async function createWindow () {
  73. try {
  74. let json = await fs.promises.readFile(path.join(__dirname,'uas.json'), 'utf8');
  75. useragents = JSON.parse(json);
  76. } catch (e){console.log(e)}
  77. protocol.handle("i",(req)=>{return null;});
  78. await (async ()=>{
  79. try{
  80. const readInterface = readline.createInterface ({
  81. input: fs.createReadStream (path.join(__dirname,'config'), 'utf8'),
  82. });
  83. for await (const line of readInterface) {
  84. addrCommand(line);
  85. }
  86. }catch(e){console.log(e);}
  87. })();
  88. win = new BrowserWindow(
  89. {width: 800, height: 600,autoHideMenuBar: true,
  90. webPreferences: {
  91. nodeIntegration: true,
  92. contextIsolation: false,
  93. webviewTag: true,
  94. }});
  95. win.setMenuBarVisibility(false);
  96. win.on('closed', function () {
  97. win = null
  98. })
  99. win.loadFile('index.html');
  100. fs.readFile(path.join(__dirname,'gredirect.json'), 'utf8', (err, jsonString) => {
  101. if (err) return;
  102. try {
  103. gredirects = JSON.parse(jsonString);
  104. } catch (e){console.log(e)}
  105. });
  106. fs.readFile(path.join(__dirname,'proxy.json'), 'utf8', (err, jsonString) => {
  107. if (err) return;
  108. try {
  109. proxies = JSON.parse(jsonString);
  110. let match = jsonString.match(/"([^"]+)"/);
  111. if(match)
  112. proxy = proxies[match[1]];
  113. } catch (e){console.log(e)}
  114. });
  115. cmdlineProcess(process.argv, process.cwd(), 0);
  116. //app.commandLine.appendSwitch ('trace-warnings');
  117. fs.readFile(path.join(__dirname,'download.json'), 'utf8', (err, jsonStr) => {
  118. if (err) return;
  119. try {
  120. downloadMenus = JSON.parse(jsonStr);
  121. }catch (e){console.log(e)}
  122. });
  123. fs.readFile(path.join(__dirname,'select.json'), 'utf8', (err, jsonStr) => {
  124. if (err) return;
  125. try {
  126. selectMenus = JSON.parse(jsonStr);
  127. }catch (e){console.log(e)}
  128. });
  129. win.webContents.on('page-title-updated',(event,cmd)=>{
  130. addrCommand(cmd);
  131. });
  132. session.defaultSession.on("will-download", async (e, item) => {
  133. //item.setSavePath(save)
  134. if(!downloadMenus) return;
  135. let menuT = downloadContextMenuTemp(item.getURL());
  136. let button = await promiseContextMenu(menuT);
  137. if(-1===button) return;
  138. e.preventDefault();
  139. });
  140. win.webContents.on('console-message',cbConsoleMsg);
  141. }
  142. app.on('window-all-closed', function () {
  143. app.quit()
  144. })
  145. app.on('activate', function () {
  146. if (win === null) {
  147. createWindow()
  148. }
  149. })
  150. app.on('will-quit', () => {
  151. })
  152. app.on ('web-contents-created', (event, contents) => {
  153. if (contents.getType () === 'webview') {
  154. contents.setWindowOpenHandler(cbWindowOpenHandler);
  155. contents.on('context-menu',onContextMenu);
  156. contents.on('page-title-updated',cbTitleUpdate);
  157. contents.session.webRequest.onBeforeRequest(interceptRequest);
  158. }
  159. });
  160. ipcMain.on('command', (event, cmd) => {
  161. addrCommand(cmd);
  162. });
  163. function addrCommand(cmd){
  164. if(cmd.length<3) return;
  165. let c0 = cmd.charCodeAt(0);
  166. switch(c0){
  167. case 33://"!"
  168. bangcommand(q,1);
  169. return;
  170. case 58: //':'
  171. args = cmd.substring(1).split(/\s+/);
  172. switch(args[0]){
  173. case "cert":
  174. if(args.length==1)
  175. session.defaultSession.setCertificateVerifyProc((request, callback) => {
  176. callback(0);
  177. });
  178. else
  179. session.defaultSession.setCertificateVerifyProc(null);
  180. return;
  181. case "clear":
  182. if(args.length==1){
  183. session.defaultSession.clearData();
  184. return;
  185. }
  186. switch(args[1]){
  187. case "cache":
  188. session.defaultSession.clearCache();
  189. return;
  190. case "dns":
  191. session.defaultSession.clearHostResolverCache();
  192. return;
  193. case "storage":
  194. session.defaultSession.clearStorageData();
  195. return;
  196. default:
  197. try {
  198. let opts = JSON.parse(args.slice(1).join(""));
  199. session.defaultSession.clearData(opts);
  200. }catch(e){console.log(e)}
  201. }
  202. return;
  203. case "exit":
  204. win.close();
  205. return;
  206. case "ext":
  207. session.defaultSession.loadExtension(args[1]);
  208. return;
  209. case "gr":
  210. if(args.length<2) {
  211. gredirect_enable(0);
  212. return;
  213. }
  214. let i = parseInt(args[1]);
  215. if(i>=0 && i<gredirects.length)
  216. gredirect_enable(i);
  217. else
  218. gredirect_disable();
  219. return;
  220. case "js"://execute js
  221. eval(cmd.slice(4));
  222. return;
  223. case "nc":
  224. bForwardCookie = false;
  225. msgbox_info("Cookie forwarding disabled");
  226. return;
  227. case "uc":
  228. if(bForwardCookie) {
  229. msgbox_info("Cookie forwarding enabled for global redirection");
  230. return;
  231. }
  232. forwardCookie();
  233. return;
  234. case "np":
  235. session.defaultSession.setProxy ({mode:"direct"});
  236. bRedirect = true;
  237. return;
  238. case "up":
  239. if(args.length>1)
  240. proxy = proxies[args[1]]; //retrieve proxy
  241. if(proxy){
  242. session.defaultSession.setProxy(proxy)
  243. .then(() => {gredirect_disable()})
  244. .catch((error) => {
  245. console.error('Failed to set proxy:', error);
  246. });
  247. }
  248. return;
  249. case "nr":
  250. bRedirect = false; return;
  251. case "ur":
  252. bRedirect = true; return;
  253. case "ua":
  254. if(args.length==2){
  255. let ua = useragents[args[1]];
  256. if(ua)
  257. session.defaultSession.setUserAgent(ua);
  258. }else
  259. session.defaultSession.setUserAgent(defaultUA);
  260. return;
  261. case "update":
  262. let updateurl;
  263. if(1==args.length)
  264. updateApp(repositoryurl);
  265. else {
  266. filename = args[1];
  267. let iSlash = filename.lastIndexOf('/');
  268. if(iSlash>0){
  269. let folder = path.join(__dirname,filename.substring(0,iSlash));
  270. fs.mkdirSync(folder,{ recursive: true });
  271. }
  272. fetch2file(repositoryurl,filename);
  273. }
  274. return;
  275. }
  276. }
  277. }
  278. function gredirect_disable(){
  279. if(gredirect){
  280. gredirect=null;
  281. unregisterHandler();
  282. }
  283. bRedirect = true;
  284. }
  285. function gredirect_enable(i){
  286. if(i>=gredirects.length) return;
  287. if(!gredirect) registerHandler();
  288. gredirect=gredirects[i];
  289. }
  290. function cbConsoleMsg(e, level, msg, line, sourceid){
  291. console.log(line);
  292. console.log(sourceid);
  293. console.log(msg);
  294. }
  295. function interceptRequest(details, callback){
  296. let url = details.url;
  297. if(58===url.charCodeAt(1) || (!bJS && url.endsWith(".js"))){
  298. callback({ cancel: true });
  299. return;
  300. }
  301. do {
  302. if(gredirect || !bRedirect ||(details.resourceType !== 'mainFrame' &&
  303. details.resourceType !== 'subFrame')) break;
  304. let oURL = new URL(url);
  305. let domain = oURL.hostname;
  306. let newUrl;
  307. try{
  308. let newDomain = redirects[domain];
  309. if(!newDomain) break;
  310. newUrl = "https://"+newDomain+oURL.pathname+oURL.search+oURL.hash;
  311. }catch(e){break;}
  312. callback({ cancel: false, redirectURL: newUrl });
  313. return;
  314. }while(false);
  315. callback({ cancel: false });
  316. }
  317. function cbWindowOpenHandler(details){
  318. let url = details.url;
  319. let js = "newTab();tabs.children[tabs.children.length-1].src='"+
  320. url+"';";
  321. switch(details.disposition){
  322. case "foreground-tab":
  323. case "new-window":
  324. js = js + "switchTab(tabs.children.length-1)";
  325. }
  326. win.webContents.executeJavaScript(js,false);
  327. return { action: "deny" };
  328. }
  329. function cbTitleUpdate(event,title){
  330. win.setTitle(title);
  331. }
  332. function menuSelection(menuTemplate, text){
  333. for(let i=0; i<selectMenus.length-1;i++){
  334. menuTemplate.push({
  335. label: selectMenus[i],
  336. click: () => {
  337. let cmd = selectMenus[i+1].replace('%s',text);
  338. let js = `handleQueries(\`${cmd}\`)`;
  339. win.webContents.executeJavaScript(js,false);
  340. }
  341. });
  342. }
  343. }
  344. function menuDownload(menuTemplate, labelprefix, linkUrl){
  345. for(let i=0; i<downloadMenus.length-1;i++){
  346. menuTemplate.push({
  347. label: labelprefix+downloadMenus[i],
  348. click: () => {
  349. let cmd = downloadMenus[i+1].replace('%u',linkUrl);
  350. let js = `handleQueries(\`${cmd}\`)`;
  351. win.webContents.executeJavaScript(js,false);
  352. }
  353. });
  354. }
  355. }
  356. function menuArray(labelprefix, linkUrl){
  357. let menuTemplate = [
  358. {
  359. label: labelprefix+translate('Open'),
  360. click: () => {
  361. shell.openExternal(linkUrl);
  362. }
  363. },
  364. {
  365. label: labelprefix+translate('Copy'),
  366. click: () => {
  367. clipboard.writeText(linkUrl);
  368. }
  369. },
  370. {
  371. label: labelprefix+translate('Download'),
  372. click: () => {
  373. win.contentView.children[i].webContents.downloadURL(linkUrl);
  374. }
  375. },
  376. ];
  377. if(downloadMenus)
  378. menuDownload(menuTemplate, labelprefix, linkUrl);
  379. return menuTemplate;
  380. }
  381. function onContextMenu(event, params){
  382. let url = params.linkURL;
  383. let mTemplate = [];
  384. if (url) {
  385. mTemplate.push({label:url,enabled:false});
  386. mTemplate.push.apply(mTemplate,menuArray("",url));
  387. if((url=params.srcURL))
  388. mTemplate.push.apply(mTemplate,menuArray("src: ",url));
  389. }else if((url=params.srcURL)){
  390. mTemplate.push({label:url,enabled:false});
  391. mTemplate.push.apply(mTemplate,menuArray("src: ",url));
  392. }else if((url=params.selectionText)){
  393. menuSelection(mTemplate,url);
  394. }else
  395. return;
  396. const contextMenu = Menu.buildFromTemplate(mTemplate);
  397. contextMenu.popup();
  398. }
  399. async function topMenu(){
  400. const menuTemplate = [];
  401. try {
  402. let json = await fs.promises.readFile(path.join(__dirname,'menu.json'), 'utf8');
  403. let menus = JSON.parse(json);
  404. if(menus.length>1){
  405. let submenu = [];
  406. for(let i=0;i<menus.length-1; i=i+2){
  407. let cmd = menus[i+1];
  408. let js = `handleQueries("${cmd}")`;
  409. submenu.push({
  410. label: menus[i], click: ()=>{
  411. win.webContents.executeJavaScript(js,false);
  412. }});
  413. }
  414. menuTemplate.push({
  415. label: translate('Tools'),
  416. submenu: submenu,
  417. });
  418. }
  419. }catch(e){console.log(e)}
  420. menuTemplate.push(
  421. {
  422. label: translate('Edit'),
  423. submenu: [
  424. { label: translate('Config folder'), click: ()=>{
  425. shell.openPath(__dirname);
  426. }},
  427. ]
  428. },
  429. {
  430. label: translate('Help'),
  431. submenu: [
  432. { label: translate('Check for updates'), click: ()=>{
  433. addrCommand(":update");
  434. }},
  435. { label: translate('Help'), accelerator: 'F1', click: ()=>{
  436. help();
  437. }},
  438. { label: translate('Stop'), accelerator: 'Ctrl+C', click: ()=>{
  439. let js="tabs.children[iTab].stop()"
  440. win.webContents.executeJavaScript(js,false)
  441. }},
  442. { label: translate('getURL'), accelerator: 'Ctrl+G', click: ()=>{
  443. let js="{let q=document.forms[0].q;q.focus();q.value=tabs.children[iTab].getURL();getWinTitle()}"
  444. win.webContents.executeJavaScript(js,false).then((r)=>{
  445. win.setTitle(r);
  446. });
  447. }},
  448. { label: translate('Select'), accelerator: 'Ctrl+L', click:()=>{
  449. win.webContents.executeJavaScript("document.forms[0].q.select()",false);
  450. }},
  451. { label: translate('New Tab'), accelerator: 'Ctrl+T', click:()=>{
  452. let js = "newTab();document.forms[0].q.select();switchTab(tabs.children.length-1)";
  453. win.webContents.executeJavaScript(js,false);
  454. }},
  455. { label: translate('Restore Tab'), accelerator: 'Ctrl+Shift+T', click:()=>{
  456. let js = "{let u=closedUrls.pop();if(u){newTab();switchTab(tabs.children.length-1);tabs.children[iTab].src=u}}";
  457. win.webContents.executeJavaScript(js,false);
  458. }},
  459. { label: translate('No redirect'), accelerator: 'Ctrl+R', click: ()=>{
  460. gredirect_disable();
  461. }},
  462. { label: translate('Redirect'), accelerator: 'Ctrl+Shift+R', click: ()=>{
  463. gredirect_enable(0);
  464. }},
  465. { label: translate('Close tab'), accelerator: 'Ctrl+W', click: ()=>{
  466. win.webContents.executeJavaScript("tabClose()",false).then((r)=>{
  467. if(""===r) win.close();
  468. else win.setTitle(r);
  469. });
  470. }},
  471. { label: translate('Next Tab'), accelerator: 'Ctrl+Tab', click: ()=>{
  472. let js="tabInc(1);getWinTitle()";
  473. win.webContents.executeJavaScript(js,false).then((r)=>{
  474. win.setTitle(r);
  475. });
  476. }},
  477. { label: translate('Previous Tab'), accelerator: 'Ctrl+Shift+Tab', click: ()=>{
  478. let js="tabDec(-1);getWinTitle()";
  479. win.webContents.executeJavaScript(js,false).then((r)=>{
  480. win.setTitle(r);
  481. });
  482. }},
  483. { label: translate('Go backward'), accelerator: 'Alt+Left', click: ()=>{
  484. let js="tabs.children[iTab].goBack()";
  485. win.webContents.executeJavaScript(js,false);
  486. }},
  487. { label: translate('Go forward'), accelerator: 'Alt+Right', click: ()=>{
  488. let js="tabs.children[iTab].goForward()";
  489. win.webContents.executeJavaScript(js,false);
  490. }},
  491. { label: translate('Zoom in'), accelerator: 'Ctrl+Shift+=', click: ()=>{
  492. let js="{let t=tabs.children[iTab];let s=t.getZoomFactor()*1.2;t.setZoomFactor(s)}";
  493. win.webContents.executeJavaScript(js,false);
  494. }},
  495. { label: translate('Zoom out'), accelerator: 'Ctrl+-', click: ()=>{
  496. let js="{let t=tabs.children[iTab];let s=t.getZoomFactor()/1.2;t.setZoomFactor(s)}";
  497. win.webContents.executeJavaScript(js,false);
  498. }},
  499. { label: translate('Default zoom'), accelerator: 'Ctrl+0', click: ()=>{
  500. let js="tabs.children[iTab].setZoomFactor(1)";
  501. win.webContents.executeJavaScript(js,false);
  502. }},
  503. { label: translate('No focus'), accelerator: 'Esc', click: ()=>{
  504. let js = `{let e=document.activeElement;
  505. if(e)e.blur();try{tabs.children[iTab].stopFindInPage('clearSelection')}catch(er){}}`;
  506. win.webContents.executeJavaScript(js,false);
  507. }},
  508. { label: translate('Reload'), accelerator: 'F5', click: ()=>{
  509. win.webContents.executeJavaScript("tabs.children[iTab].reload()",false);
  510. }},
  511. { label: translate('Devtools'), accelerator: 'F12', click: ()=>{
  512. let js = "try{tabs.children[iTab].openDevTools()}catch(e){console.log(e)}";
  513. win.webContents.executeJavaScript(js,false);
  514. }},
  515. ],
  516. },
  517. );
  518. const menu = Menu.buildFromTemplate(menuTemplate);
  519. Menu.setApplicationMenu(menu);
  520. }
  521. function cmdlineProcess(argv,cwd,extra){
  522. let i1st = 2+extra; //index for the first query item
  523. if(argv.length>i1st){
  524. if(i1st+1==argv.length){//local file
  525. let fname = path.join(cwd, argv[i1st]);
  526. if(fs.existsSync(fname)){
  527. let js = "tabs.children[iTab].src='file://"+fname+"'";
  528. win.webContents.executeJavaScript(js,false);
  529. win.setTitle(argv[i1st]);
  530. return;
  531. }
  532. }
  533. let url=argv.slice(i1st).join(" ");
  534. win.webContents.executeJavaScript("handleQuery(`"+url+"`)",false);
  535. win.setTitle(url);
  536. }
  537. }
  538. async function cbScheme_redir(req){
  539. if(!gredirect) return null;
  540. let oUrl = req.url;
  541. let newurl = gredirect+oUrl;
  542. const parsedUrl = url.parse(newurl);
  543. const options = {
  544. hostname: parsedUrl.hostname,
  545. port: parsedUrl.port,
  546. path: parsedUrl.path,
  547. method: req.method,
  548. headers: req.headers
  549. };
  550. if(bForwardCookie){
  551. let cookies = await session.defaultSession.cookies.get({url: oUrl});
  552. let cookieS = cookies.map (cookie => cookie.name + '=' + cookie.value ).join(';');
  553. options.headers['cookie']=cookieS;
  554. }
  555. return new Promise((resolve, reject) => {
  556. const nreq = https.request(options, (res) => {
  557. let body = [];
  558. res.on('data', (chunk) => {
  559. body.push(chunk);
  560. });
  561. res.on('end', () => {
  562. try {
  563. body = Buffer.concat(body);
  564. const response = new Response(body, {
  565. status: res.status,
  566. statusText: res.statusText,
  567. headers: res.headers,
  568. });
  569. resolve(response);
  570. } catch (e) {
  571. reject(e);
  572. }
  573. });
  574. });
  575. nreq.on('error', (err) => {
  576. reject(err);
  577. });
  578. if (req.body){
  579. const reader = req.body.getReader();
  580. reader.read().then(function processText({ done, value }) {
  581. if (done) {
  582. nreq.end();
  583. return;
  584. }
  585. nreq.write(value);
  586. });
  587. }else
  588. nreq.end();
  589. });
  590. }
  591. function registerHandler(){
  592. protocol.handle("http",cbScheme_redir);
  593. protocol.handle("https",cbScheme_redir);
  594. protocol.handle("ws",cbScheme_redir);
  595. protocol.handle("wss",cbScheme_redir);
  596. }
  597. function unregisterHandler(){
  598. protocol.unhandle("http",cbScheme_redir);
  599. protocol.unhandle("https",cbScheme_redir);
  600. protocol.unhandle("ws",cbScheme_redir);
  601. protocol.unhandle("wss",cbScheme_redir);
  602. }
  603. function forwardCookie(){
  604. const choice = dialog.showMessageBoxSync(null, {
  605. type: 'warning',
  606. title: 'Confirm cookie forwarding with global redirection',
  607. message: 'Cookies are used to access your account. Forwarding cookies is vulnerable to global redirection server, proceed to enable cookie forwarding with global redirection?',
  608. buttons: ['No','Yes']
  609. })
  610. if(1===choice) bForwardCookie=true;
  611. }
  612. function msgbox_info(msg){
  613. dialog.showMessageBoxSync(null, {
  614. type: 'info',
  615. title: msg,
  616. message: msg,
  617. buttons: ['OK']
  618. })
  619. }
  620. async function updateApp(url){//url must ending with "/"
  621. let msg;
  622. do {
  623. try {
  624. let res = await fetch(url+"package.json");
  625. let packageS = await res.text();
  626. {//the last part of version string is the version number, must keep increasing
  627. let head = packageS.slice(2,40);
  628. let iV = head.indexOf("version");
  629. if(iV<0) {
  630. msg = "remote package.json corrupted"
  631. break;
  632. }
  633. iV = iV + 10;
  634. let iE = head.indexOf('"',iV+4);
  635. let iS = head.lastIndexOf('.',iE-1);
  636. let nLatestVer = parseInt(head.substring(iS+1,iE));
  637. let ver = app.getVersion();
  638. iS = ver.lastIndexOf('.');
  639. let nVer = parseInt(ver.substring(iS+1));
  640. if(nVer>=nLatestVer){
  641. msg = `Current version ${ver} is already up to date`;
  642. break;
  643. }
  644. const choice = dialog.showMessageBoxSync(null, {
  645. type: 'warning',
  646. title: `Update from ${url}`,
  647. message: `Proceed to update from ${ver} to ${head.substring(iV,iE)}?`,
  648. buttons: ['YES','NO']
  649. });
  650. if(1===choice) return;
  651. }
  652. writeFile("package.json", packageS);
  653. fetch2file(url,"webview.js");
  654. fetch2file(url,"index.html");
  655. msg = "Update completed";
  656. }catch(e){
  657. msg = "Fail to update"
  658. }
  659. }while(false);
  660. dialog.showMessageBoxSync(null, {
  661. type: 'info',
  662. title: `Update from ${url}`,
  663. message: msg,
  664. buttons: ['OK']
  665. })
  666. }
  667. async function fetch2file(urlFolder, filename, bOverwritten=true){
  668. let pathname=path.join(__dirname,filename);
  669. if(!bOverwritten && fs.existsSync(pathname)) return;
  670. let res = await fetch(urlFolder+filename);
  671. let str = await res.text();
  672. writeFile(pathname, str);
  673. }
  674. async function writeFile(filename, str){
  675. let pathname=filename+".new";
  676. fs.writeFile(pathname, str, (err) => {
  677. if(err) throw "Fail to write";
  678. fs.rename(pathname,filename,(e1)=>{
  679. if(e1) throw "Fail to rename";
  680. });
  681. });
  682. }
  683. function help(){
  684. const readme = "README.md";
  685. const htmlFN = path.join(__dirname,readme);
  686. let js=`{let t=tabs.children[iTab];t.dataset.jsonce=BML_md;t.src="file://${htmlFN}"}`;
  687. win.webContents.executeJavaScript(js,false)
  688. }
  689. function downloadContextMenuTemp(url){
  690. let mTemplate =
  691. [{label:url,enabled:false},
  692. {label: translate('Download')},
  693. {
  694. label: translate('Copy'),
  695. click: () => {
  696. clipboard.writeText(url);
  697. }
  698. },
  699. ];
  700. menuDownload(mTemplate, "", url);
  701. return mTemplate;
  702. }
  703. async function initTranslateRes(lang){
  704. let basename=path.join(__dirname,"translate.");
  705. let fname = basename+lang;
  706. if(!fs.existsSync(fname))
  707. fname = basename+lang.slice(0,2);
  708. try {
  709. let json = await fs.promises.readFile(fname,'utf8');
  710. translateRes = JSON.parse(json);
  711. } catch (e){}
  712. topMenu();
  713. }
  714. function translate(str){
  715. let result;
  716. if(translateRes && (result=translateRes[str])) return result;
  717. return str;
  718. }
  719. function promiseContextMenu(menuTemplate) {
  720. return new Promise((resolve, reject) => {
  721. menuTemplate[1].click = () => resolve(-1);
  722. const menu = Menu.buildFromTemplate(menuTemplate);
  723. menu.on('menu-will-close', () => resolve(-2));
  724. menu.popup();
  725. });
  726. }
  727. function httpReq(url, method, filePath){
  728. fs.readFile(filePath, (err, fileData) => {
  729. if (err) {
  730. console.error(`Error reading file: ${err.message}`);
  731. return;
  732. }
  733. let opts = {
  734. method: method,
  735. headers: {
  736. "Content-Type":'application/octet-stream',
  737. },
  738. body: fileData,
  739. };
  740. fetch(url,opts);
  741. });
  742. }
  743. function bangcommand(q,offset){
  744. let iS = q.indexOf(' ',offset);
  745. if(iS<0) iS=q.length;
  746. let fname = q.substring(offset,iS);
  747. let fpath = path.join(__dirname,fname+'.js');
  748. if (fs.existsSync(fpath)) {
  749. fs.readFile(fpath, 'utf8',(err, js)=>{
  750. if (err) {
  751. console.log(err);
  752. return;
  753. }
  754. const prefix = "(function(){";
  755. const postfix = "})(`";
  756. const end ="`)";
  757. const fjs = `${prefix}${js}${postfix}${q}${end}`;
  758. eval(fjs);
  759. });
  760. }
  761. }