webview.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  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. async 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. let iS = cmd.indexOf(' ',1);
  172. if(iS<0) iS = cmd.length;
  173. let arg0 = cmd.substring(1,iS);
  174. switch(arg0){
  175. case "cert":
  176. if(cmd.length==iS)
  177. session.defaultSession.setCertificateVerifyProc((request, callback) => {
  178. callback(0);
  179. });
  180. else
  181. session.defaultSession.setCertificateVerifyProc(null);
  182. return;
  183. case "clear":
  184. if(cmd.length<=iS+1){
  185. session.defaultSession.clearData();
  186. return;
  187. }
  188. if(123===cmd.charCodeAt(iS+1)){//json
  189. try {
  190. let opts = JSON.parse(cmd.substring(iS+1));
  191. session.defaultSession.clearData(opts);
  192. }catch(e){console.log(e)}
  193. return;
  194. }
  195. let args = cmd.substring(iS+1).split(/\s+/);
  196. switch(args[0]){
  197. case "cache":
  198. session.defaultSession.clearCache();
  199. return;
  200. case "cookie":
  201. if(args.length==1){
  202. session.defaultSession.clearStorageData({ storages: ['cookies'] });
  203. return;
  204. }
  205. {
  206. let url = args[1];
  207. if(url.charCodeAt(0)!==104) url = "https://"+url;
  208. session.defaultSession.cookies.get({ url: url }).then((cookies) => {
  209. cookies.forEach((cookie) => {
  210. session.defaultSession.cookies.remove(targetUrl, cookie.name)})});
  211. }
  212. return;
  213. case "dns":
  214. session.defaultSession.clearHostResolverCache();
  215. return;
  216. case "storage":
  217. session.defaultSession.clearStorageData();
  218. return;
  219. default:
  220. }
  221. return;
  222. case "exit":
  223. win.close();
  224. return;
  225. case "ext":
  226. session.defaultSession.loadExtension(cmd.substring(iS+1));
  227. return;
  228. case "gr":
  229. if(cmd.length==iS) {
  230. gredirect_enable(0);
  231. return;
  232. }
  233. let i = parseInt(cmd.substring(iS+1));
  234. if(i>=0 && i<gredirects.length)
  235. gredirect_enable(i);
  236. else
  237. gredirect_disable();
  238. return;
  239. case "js"://execute js
  240. eval(cmd.slice(4));
  241. return;
  242. case "nc":
  243. bForwardCookie = false;
  244. msgbox_info("Cookie forwarding disabled");
  245. return;
  246. case "uc":
  247. if(bForwardCookie) {
  248. msgbox_info("Cookie forwarding enabled for global redirection");
  249. return;
  250. }
  251. forwardCookie();
  252. return;
  253. case "np":
  254. session.defaultSession.setProxy ({mode:"direct"});
  255. bRedirect = true;
  256. return;
  257. case "up":
  258. if(cmd.length>iS)
  259. proxy = proxies[cmd.substring(iS+1)]; //retrieve proxy
  260. if(proxy){
  261. session.defaultSession.setProxy(proxy)
  262. .then(() => {gredirect_disable()})
  263. .catch((error) => {
  264. console.error('Failed to set proxy:', error);
  265. });
  266. }
  267. return;
  268. case "nr":
  269. bRedirect = false; return;
  270. case "ur":
  271. bRedirect = true; return;
  272. case "sys":
  273. {
  274. let iHTTP = cmd.search(/https?:\/\//);
  275. if(iHTTP<0) return;
  276. let iEnd = cmd.indexOf(' ',iHTTP+10);
  277. if(iEnd<0) iEnd = cmd.length;
  278. let url = cmd.substring(iHTTP,iEnd);
  279. let cookies = await session.defaultSession.cookies.get({url: url});
  280. let cookieS = cookies.map (cookie => cookie.name + '='
  281. + cookie.value ).join(';');
  282. let args = cmd.substring(5).split(/\s+/);
  283. for(let i=1;i<args.length;i++){
  284. let iC = args[i].indexOf('%cookie');
  285. if(iC<0) continue;
  286. args[i] = args[i].substring(0,i)+cookieS+args[i].substring(i+7);
  287. break;
  288. }
  289. const { spawn } = require('child_process');
  290. const process = spawn(args[0],args.slice(1));
  291. process.stdout.on('data', (data) => {
  292. let str = data.toString();
  293. console.log(str);
  294. let js = "showHtml(`"+str+"`)";
  295. win.webContents.executeJavaScript(js,false);
  296. });
  297. }
  298. return;
  299. case "ua":
  300. if(cmd.length>iS){
  301. let ua = useragents[cmd.substring(iS+1)];
  302. if(ua)
  303. session.defaultSession.setUserAgent(ua);
  304. }else
  305. session.defaultSession.setUserAgent(defaultUA);
  306. return;
  307. case "update":
  308. let updateurl;
  309. if(cmd.length==iS)
  310. updateApp(repositoryurl);
  311. else {
  312. filename = cmd.substring(iS+1);
  313. let iSlash = filename.lastIndexOf('/');
  314. if(iSlash>0){
  315. let folder = path.join(__dirname,filename.substring(0,iSlash));
  316. fs.mkdirSync(folder,{ recursive: true });
  317. }
  318. fetch2file(repositoryurl,filename);
  319. }
  320. return;
  321. }
  322. }
  323. }
  324. function gredirect_disable(){
  325. if(gredirect){
  326. gredirect=null;
  327. unregisterHandler();
  328. }
  329. bRedirect = true;
  330. }
  331. function gredirect_enable(i){
  332. if(i>=gredirects.length) return;
  333. if(!gredirect) registerHandler();
  334. gredirect=gredirects[i];
  335. }
  336. function cbConsoleMsg(e, level, msg, line, sourceid){
  337. console.log(line);
  338. console.log(sourceid);
  339. console.log(msg);
  340. }
  341. function interceptRequest(details, callback){
  342. let url = details.url;
  343. if(58===url.charCodeAt(1) || (!bJS && url.endsWith(".js"))){
  344. callback({ cancel: true });
  345. return;
  346. }
  347. do {
  348. if(gredirect || !bRedirect ||(details.resourceType !== 'mainFrame' &&
  349. details.resourceType !== 'subFrame')) break;
  350. let oURL = new URL(url);
  351. let domain = oURL.hostname;
  352. let newUrl;
  353. try{
  354. let newDomain = redirects[domain];
  355. if(!newDomain) break;
  356. newUrl = "https://"+newDomain+oURL.pathname+oURL.search+oURL.hash;
  357. }catch(e){break;}
  358. callback({ cancel: false, redirectURL: newUrl });
  359. return;
  360. }while(false);
  361. callback({ cancel: false });
  362. }
  363. function cbWindowOpenHandler(details){
  364. let url = details.url;
  365. let js = "newTab();tabs.children[tabs.children.length-1].src='"+
  366. url+"';";
  367. switch(details.disposition){
  368. case "foreground-tab":
  369. case "new-window":
  370. js = js + "switchTab(tabs.children.length-1)";
  371. }
  372. win.webContents.executeJavaScript(js,false);
  373. return { action: "deny" };
  374. }
  375. function cbTitleUpdate(event,title){
  376. win.setTitle(title);
  377. }
  378. function menuSelection(menuTemplate, text){
  379. for(let i=0; i<selectMenus.length-1;i++){
  380. menuTemplate.push({
  381. label: selectMenus[i],
  382. click: () => {
  383. let cmd = selectMenus[i+1].replace('%s',text);
  384. let js = `handleQueries(\`${cmd}\`)`;
  385. win.webContents.executeJavaScript(js,false);
  386. }
  387. });
  388. }
  389. }
  390. function menuDownload(menuTemplate, labelprefix, linkUrl){
  391. for(let i=0; i<downloadMenus.length-1;i++){
  392. menuTemplate.push({
  393. label: labelprefix+downloadMenus[i],
  394. click: () => {
  395. let cmd = downloadMenus[i+1].replace('%u',linkUrl);
  396. let js = `handleQueries(\`${cmd}\`)`;
  397. win.webContents.executeJavaScript(js,false);
  398. }
  399. });
  400. }
  401. }
  402. function menuArray(labelprefix, linkUrl){
  403. let menuTemplate = [
  404. {
  405. label: labelprefix+translate('Open'),
  406. click: () => {
  407. shell.openExternal(linkUrl);
  408. }
  409. },
  410. {
  411. label: labelprefix+translate('Copy'),
  412. click: () => {
  413. clipboard.writeText(linkUrl);
  414. }
  415. },
  416. {
  417. label: labelprefix+translate('Download'),
  418. click: () => {
  419. win.webContents.downloadURL(linkUrl);
  420. }
  421. },
  422. ];
  423. if(downloadMenus)
  424. menuDownload(menuTemplate, labelprefix, linkUrl);
  425. return menuTemplate;
  426. }
  427. function onContextMenu(event, params){
  428. let url = params.linkURL;
  429. let mTemplate = [];
  430. if (url) {
  431. mTemplate.push({label:url,enabled:false});
  432. mTemplate.push.apply(mTemplate,menuArray("",url));
  433. if((url=params.srcURL))
  434. mTemplate.push.apply(mTemplate,menuArray("src: ",url));
  435. }else if((url=params.srcURL)){
  436. mTemplate.push({label:url,enabled:false});
  437. mTemplate.push.apply(mTemplate,menuArray("src: ",url));
  438. }else if((url=params.selectionText)){
  439. menuSelection(mTemplate,url);
  440. }else
  441. return;
  442. const contextMenu = Menu.buildFromTemplate(mTemplate);
  443. contextMenu.popup();
  444. }
  445. async function topMenu(){
  446. const menuTemplate = [];
  447. try {
  448. let json = await fs.promises.readFile(path.join(__dirname,'menu.json'), 'utf8');
  449. let menus = JSON.parse(json);
  450. if(menus.length>1){
  451. let submenu = [];
  452. for(let i=0;i<menus.length-1; i=i+2){
  453. let cmd = menus[i+1];
  454. let js = `handleQueries("${cmd}")`;
  455. submenu.push({
  456. label: menus[i], click: ()=>{
  457. win.webContents.executeJavaScript(js,false);
  458. }});
  459. }
  460. menuTemplate.push({
  461. label: translate('Tools'),
  462. submenu: submenu,
  463. });
  464. }
  465. }catch(e){console.log(e)}
  466. menuTemplate.push(
  467. {
  468. label: translate('Edit'),
  469. submenu: [
  470. { label: translate('Config folder'), click: ()=>{
  471. shell.openPath(__dirname);
  472. }},
  473. ]
  474. },
  475. {
  476. label: translate('Help'),
  477. submenu: [
  478. { label: translate('Check for updates'), click: ()=>{
  479. addrCommand(":update");
  480. }},
  481. { label: translate('Help'), accelerator: 'F1', click: ()=>{
  482. help();
  483. }},
  484. { label: translate('Stop'), accelerator: 'Ctrl+C', click: ()=>{
  485. let js="tabs.children[iTab].stop()"
  486. win.webContents.executeJavaScript(js,false)
  487. }},
  488. { label: translate('getURL'), accelerator: 'Ctrl+G', click: ()=>{
  489. let js="{let q=document.forms[0].q;q.focus();q.value=tabs.children[iTab].getURL();getWinTitle()}"
  490. win.webContents.executeJavaScript(js,false).then((r)=>{
  491. win.setTitle(r);
  492. });
  493. }},
  494. { label: translate('Select'), accelerator: 'Ctrl+L', click:()=>{
  495. win.webContents.executeJavaScript("document.forms[0].q.select()",false);
  496. }},
  497. { label: translate('New Tab'), accelerator: 'Ctrl+T', click:()=>{
  498. let js = "newTab();document.forms[0].q.select();switchTab(tabs.children.length-1)";
  499. win.webContents.executeJavaScript(js,false);
  500. }},
  501. { label: translate('Restore Tab'), accelerator: 'Ctrl+Shift+T', click:()=>{
  502. let js = "{let u=closedUrls.pop();if(u){newTab();switchTab(tabs.children.length-1);tabs.children[iTab].src=u}}";
  503. win.webContents.executeJavaScript(js,false);
  504. }},
  505. { label: translate('No redirect'), accelerator: 'Ctrl+R', click: ()=>{
  506. gredirect_disable();
  507. }},
  508. { label: translate('Redirect'), accelerator: 'Ctrl+Shift+R', click: ()=>{
  509. gredirect_enable(0);
  510. }},
  511. { label: translate('Close tab'), accelerator: 'Ctrl+W', click: ()=>{
  512. win.webContents.executeJavaScript("tabClose()",false).then((r)=>{
  513. if(""===r) win.close();
  514. else win.setTitle(r);
  515. });
  516. }},
  517. { label: translate('Next Tab'), accelerator: 'Ctrl+Tab', click: ()=>{
  518. let js="tabInc(1);getWinTitle()";
  519. win.webContents.executeJavaScript(js,false).then((r)=>{
  520. win.setTitle(r);
  521. });
  522. }},
  523. { label: translate('Previous Tab'), accelerator: 'Ctrl+Shift+Tab', click: ()=>{
  524. let js="tabDec(-1);getWinTitle()";
  525. win.webContents.executeJavaScript(js,false).then((r)=>{
  526. win.setTitle(r);
  527. });
  528. }},
  529. { label: translate('Go backward'), accelerator: 'Alt+Left', click: ()=>{
  530. let js="tabs.children[iTab].goBack()";
  531. win.webContents.executeJavaScript(js,false);
  532. }},
  533. { label: translate('Go forward'), accelerator: 'Alt+Right', click: ()=>{
  534. let js="tabs.children[iTab].goForward()";
  535. win.webContents.executeJavaScript(js,false);
  536. }},
  537. { label: translate('Zoom in'), accelerator: 'Ctrl+Shift+=', click: ()=>{
  538. let js="{let t=tabs.children[iTab];let s=t.getZoomFactor()*1.2;t.setZoomFactor(s)}";
  539. win.webContents.executeJavaScript(js,false);
  540. }},
  541. { label: translate('Zoom out'), accelerator: 'Ctrl+-', click: ()=>{
  542. let js="{let t=tabs.children[iTab];let s=t.getZoomFactor()/1.2;t.setZoomFactor(s)}";
  543. win.webContents.executeJavaScript(js,false);
  544. }},
  545. { label: translate('Default zoom'), accelerator: 'Ctrl+0', click: ()=>{
  546. let js="tabs.children[iTab].setZoomFactor(1)";
  547. win.webContents.executeJavaScript(js,false);
  548. }},
  549. { label: translate('No focus'), accelerator: 'Esc', click: ()=>{
  550. let js = `{let e=document.activeElement;
  551. if(e)e.blur();try{tabs.children[iTab].stopFindInPage('clearSelection')}catch(er){}}`;
  552. win.webContents.executeJavaScript(js,false);
  553. }},
  554. { label: translate('Reload'), accelerator: 'F5', click: ()=>{
  555. win.webContents.executeJavaScript("tabs.children[iTab].reload()",false);
  556. }},
  557. { label: translate('Devtools'), accelerator: 'F12', click: ()=>{
  558. let js = "try{tabs.children[iTab].openDevTools()}catch(e){console.log(e)}";
  559. win.webContents.executeJavaScript(js,false);
  560. }},
  561. ],
  562. },
  563. );
  564. const menu = Menu.buildFromTemplate(menuTemplate);
  565. Menu.setApplicationMenu(menu);
  566. }
  567. function cmdlineProcess(argv,cwd,extra){
  568. let i1st = 2+extra; //index for the first query item
  569. if(argv.length>i1st){
  570. if(i1st+1==argv.length){//local file
  571. let fname = path.join(cwd, argv[i1st]);
  572. if(fs.existsSync(fname)){
  573. let js = "tabs.children[iTab].src='file://"+fname+"'";
  574. win.webContents.executeJavaScript(js,false);
  575. win.setTitle(argv[i1st]);
  576. return;
  577. }
  578. }
  579. let url=argv.slice(i1st).join(" ");
  580. win.webContents.executeJavaScript("handleQuery(`"+url+"`)",false);
  581. win.setTitle(url);
  582. }
  583. }
  584. async function cbScheme_redir(req){
  585. if(!gredirect) return null;
  586. let oUrl = req.url;
  587. let newurl = gredirect+oUrl;
  588. const parsedUrl = url.parse(newurl);
  589. let headers = new Headers();
  590. for (var pair of req.headers.entries())
  591. headers.set(pair[0],pair[1]);
  592. if(bForwardCookie){
  593. let cookies = await session.defaultSession.cookies.get({url: oUrl});
  594. let cookieS = cookies.map (cookie => cookie.name + '=' + cookie.value ).join(';');
  595. headers.set('cookie',cookieS);
  596. }
  597. //missing referer header
  598. //headers.set('referer',);
  599. const options = {
  600. hostname: parsedUrl.hostname,
  601. port: parsedUrl.port,
  602. path: parsedUrl.path,
  603. method: req.method,
  604. headers: headers
  605. };
  606. return new Promise(async (resolve, reject) => {
  607. const nreq = https.request(options, (res) => {
  608. let body = [];
  609. res.on('data', (chunk) => {
  610. body.push(chunk);
  611. });
  612. res.on('end', () => {
  613. try {
  614. body = Buffer.concat(body);
  615. const response = new Response(body, res);
  616. resolve(response);
  617. } catch (e) {
  618. reject(e);
  619. }
  620. });
  621. });
  622. nreq.on('error', (err) => {
  623. reject(err);
  624. });
  625. if (req.body){
  626. try {
  627. const reader = req.body.getReader();
  628. do {
  629. const { done, value } = await reader.read();
  630. if (done) {
  631. nreq.end();
  632. break;
  633. }
  634. nreq.write(value);
  635. console.log(headers);
  636. console.log(new TextDecoder("iso-8859-1").decode(value));
  637. }while (true);
  638. }catch(e){reject(e)}
  639. }else
  640. nreq.end();
  641. });
  642. }
  643. function registerHandler(){
  644. protocol.handle("http",cbScheme_redir);
  645. protocol.handle("https",cbScheme_redir);
  646. protocol.handle("ws",cbScheme_redir);
  647. protocol.handle("wss",cbScheme_redir);
  648. }
  649. function unregisterHandler(){
  650. protocol.unhandle("http",cbScheme_redir);
  651. protocol.unhandle("https",cbScheme_redir);
  652. protocol.unhandle("ws",cbScheme_redir);
  653. protocol.unhandle("wss",cbScheme_redir);
  654. }
  655. function forwardCookie(){
  656. const choice = dialog.showMessageBoxSync(null, {
  657. type: 'warning',
  658. title: 'Confirm cookie forwarding with global redirection',
  659. message: 'Cookies are used to access your account. Forwarding cookies is vulnerable to global redirection server, proceed to enable cookie forwarding with global redirection?',
  660. buttons: ['No','Yes']
  661. })
  662. if(1===choice) bForwardCookie=true;
  663. }
  664. function msgbox_info(msg){
  665. dialog.showMessageBoxSync(null, {
  666. type: 'info',
  667. title: msg,
  668. message: msg,
  669. buttons: ['OK']
  670. })
  671. }
  672. async function updateApp(url){//url must ending with "/"
  673. let msg;
  674. do {
  675. try {
  676. let res = await fetch(url+"package.json");
  677. let packageS = await res.text();
  678. {//the last part of version string is the version number, must keep increasing
  679. let head = packageS.slice(2,40);
  680. let iV = head.indexOf("version");
  681. if(iV<0) {
  682. msg = "remote package.json corrupted"
  683. break;
  684. }
  685. iV = iV + 10;
  686. let iE = head.indexOf('"',iV+4);
  687. let iS = head.lastIndexOf('.',iE-1);
  688. let nLatestVer = parseInt(head.substring(iS+1,iE));
  689. let ver = app.getVersion();
  690. iS = ver.lastIndexOf('.');
  691. let nVer = parseInt(ver.substring(iS+1));
  692. if(nVer>=nLatestVer){
  693. msg = `Current version ${ver} is already up to date`;
  694. break;
  695. }
  696. const choice = dialog.showMessageBoxSync(null, {
  697. type: 'warning',
  698. title: `Update from ${url}`,
  699. message: `Proceed to update from ${ver} to ${head.substring(iV,iE)}?`,
  700. buttons: ['YES','NO']
  701. });
  702. if(1===choice) return;
  703. }
  704. writeFile("package.json", packageS);
  705. fetch2file(url,"webview.js");
  706. fetch2file(url,"index.html");
  707. msg = "Update completed";
  708. }catch(e){
  709. msg = "Fail to update"
  710. }
  711. }while(false);
  712. dialog.showMessageBoxSync(null, {
  713. type: 'info',
  714. title: `Update from ${url}`,
  715. message: msg,
  716. buttons: ['OK']
  717. })
  718. }
  719. async function fetch2file(urlFolder, filename, bOverwritten=true){
  720. let pathname=path.join(__dirname,filename);
  721. if(!bOverwritten && fs.existsSync(pathname)) return;
  722. let res = await fetch(urlFolder+filename);
  723. let str = await res.text();
  724. writeFile(pathname, str);
  725. }
  726. async function writeFile(filename, str){
  727. let pathname=filename+".new";
  728. fs.writeFile(pathname, str, (err) => {
  729. if(err) throw "Fail to write";
  730. fs.rename(pathname,filename,(e1)=>{
  731. if(e1) throw "Fail to rename";
  732. });
  733. });
  734. }
  735. function help(){
  736. const readme = "README.md";
  737. const htmlFN = path.join(__dirname,readme);
  738. let js=`{let t=tabs.children[iTab];t.dataset.jsonce=BML_md;t.src="file://${htmlFN}"}`;
  739. win.webContents.executeJavaScript(js,false)
  740. }
  741. function downloadContextMenuTemp(url){
  742. let mTemplate =
  743. [{label:url,enabled:false},
  744. {label: translate('Download')},
  745. {
  746. label: translate('Copy'),
  747. click: () => {
  748. clipboard.writeText(url);
  749. }
  750. },
  751. ];
  752. menuDownload(mTemplate, "", url);
  753. return mTemplate;
  754. }
  755. async function initTranslateRes(lang){
  756. let basename=path.join(__dirname,"translate.");
  757. let fname = basename+lang;
  758. if(!fs.existsSync(fname))
  759. fname = basename+lang.slice(0,2);
  760. try {
  761. let json = await fs.promises.readFile(fname,'utf8');
  762. translateRes = JSON.parse(json);
  763. } catch (e){}
  764. topMenu();
  765. }
  766. function translate(str){
  767. let result;
  768. if(translateRes && (result=translateRes[str])) return result;
  769. return str;
  770. }
  771. function promiseContextMenu(menuTemplate) {
  772. return new Promise((resolve, reject) => {
  773. menuTemplate[1].click = () => resolve(-1);
  774. const menu = Menu.buildFromTemplate(menuTemplate);
  775. menu.on('menu-will-close', () => resolve(-2));
  776. menu.popup();
  777. });
  778. }
  779. function httpReq(url, method, filePath){
  780. fs.readFile(filePath, (err, fileData) => {
  781. if (err) {
  782. console.error(`Error reading file: ${err.message}`);
  783. return;
  784. }
  785. let opts = {
  786. method: method,
  787. headers: {
  788. "Content-Type":'application/octet-stream',
  789. },
  790. body: fileData,
  791. };
  792. fetch(url,opts);
  793. });
  794. }
  795. function bangcommand(q,offset){
  796. let iS = q.indexOf(' ',offset);
  797. if(iS<0) iS=q.length;
  798. let fname = q.substring(offset,iS);
  799. let fpath = path.join(__dirname,fname+'.js');
  800. if (fs.existsSync(fpath)) {
  801. fs.readFile(fpath, 'utf8',(err, js)=>{
  802. if (err) {
  803. console.log(err);
  804. return;
  805. }
  806. const prefix = "(function(){";
  807. const postfix = "})(`";
  808. const end ="`)";
  809. const fjs = `${prefix}${js}${postfix}${q}${end}`;
  810. eval(fjs);
  811. });
  812. }
  813. }