webview.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /* Copyright (C) 2024 Richard Hao Cao
  2. */
  3. const {
  4. app, BrowserWindow, Menu, shell, clipboard,
  5. session, protocol, net, dialog
  6. } = require('electron')
  7. let win;
  8. if(!app.requestSingleInstanceLock())
  9. app.quit()
  10. else {
  11. app.on('ready', createWindow);
  12. app.on('second-instance', (event, args, cwd) => {
  13. if (win) {
  14. if (win.isMinimized()) {
  15. win.restore()
  16. }
  17. win.show()
  18. win.focus()
  19. cmdlineProcess(args,cwd,1);
  20. }else
  21. createWindow();
  22. })
  23. }
  24. topMenu();
  25. const fs = require('fs');
  26. const readline = require('readline');
  27. const path = require('path')
  28. const process = require('process')
  29. var gredirects = [];
  30. var gredirect;
  31. var redirects;
  32. var bRedirect = true;
  33. var bJS = true;
  34. var bHistory = false;
  35. var bForwardCookie = false;
  36. var proxies = {};
  37. var proxy;
  38. var useragents = {};
  39. var defaultUA =
  40. "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" +
  41. process.versions.chrome +" Safari/537.36";
  42. app.userAgentFallback = defaultUA;
  43. var historyFile = path.join(__dirname,'history.rec');
  44. fs.readFile(path.join(__dirname,'redirect.json'), 'utf8', (err, jsonString) => {
  45. if (err) return;
  46. try {
  47. redirects = JSON.parse(jsonString);
  48. } catch (e){console.log(e)}
  49. });
  50. async function createWindow () {
  51. let json = await fs.promises.readFile(path.join(__dirname,'uas.json'), 'utf8');
  52. try {
  53. useragents = JSON.parse(json);
  54. } catch (e){console.log(e)}
  55. await (async ()=>{
  56. try{
  57. const readInterface = readline.createInterface ({
  58. input: fs.createReadStream (path.join(__dirname,'config'), 'utf8'),
  59. });
  60. for await (const line of readInterface) {
  61. addrCommand(line);
  62. }
  63. }catch(e){console.log(e);}
  64. })();
  65. win = new BrowserWindow(
  66. {width: 800, height: 600,autoHideMenuBar: true,
  67. webPreferences: {
  68. nodeIntegration: true,
  69. contextIsolation: false,
  70. webviewTag: true,
  71. }});
  72. win.setMenuBarVisibility(false);
  73. win.on('closed', function () {
  74. win = null
  75. })
  76. win.loadFile('index.html');
  77. fs.readFile(path.join(__dirname,'gredirect.json'), 'utf8', (err, jsonString) => {
  78. if (err) return;
  79. try {
  80. gredirects = JSON.parse(jsonString);
  81. } catch (e){console.log(e)}
  82. });
  83. fs.readFile(path.join(__dirname,'proxy.json'), 'utf8', (err, jsonString) => {
  84. if (err) return;
  85. try {
  86. proxies = JSON.parse(jsonString, (key,val)=>{
  87. if(!proxy && key==="proxyRules"){
  88. proxy = {proxyRules:val};
  89. }
  90. return val;
  91. });
  92. } catch (e){console.log(e)}
  93. });
  94. cmdlineProcess(process.argv, process.cwd(), 0);
  95. //app.commandLine.appendSwitch ('trace-warnings');
  96. win.webContents.on('page-title-updated',(event,cmd)=>{
  97. addrCommand(cmd);
  98. });
  99. win.webContents.on('console-message',cbConsoleMsg);
  100. }
  101. app.on('window-all-closed', function () {
  102. app.quit()
  103. })
  104. app.on('activate', function () {
  105. if (win === null) {
  106. createWindow()
  107. }
  108. })
  109. app.on('will-quit', () => {
  110. })
  111. app.on ('web-contents-created', (event, contents) => {
  112. if (contents.getType () === 'webview') {
  113. contents.setWindowOpenHandler(cbWindowOpenHandler);
  114. contents.on('context-menu',onContextMenu);
  115. contents.on('page-title-updated',cbTitleUpdate);
  116. //contents.on('console-message',cbConsoleMsg);
  117. //contents.on('focus', ()=>{cbFocus(contents)});
  118. //contents.on('blur',()=>{cbBlur()});
  119. contents.session.webRequest.onBeforeRequest(interceptRequest);
  120. contents.on('did-finish-load',()=>{cbFinishLoad(contents)});
  121. }
  122. });
  123. function addrCommand(cmd){
  124. if(cmd.length<3) return;
  125. let c0 = cmd.charCodeAt(0);
  126. switch(c0){
  127. case 58: //':'
  128. args = cmd.substring(1).split(/\s+/);
  129. switch(args[0]){
  130. case "cert":
  131. if(args.length==1)
  132. session.defaultSession.setCertificateVerifyProc((request, callback) => {
  133. callback(0);
  134. });
  135. else
  136. session.defaultSession.setCertificateVerifyProc(null);
  137. return;
  138. case "clear":
  139. if(args.length==1){
  140. session.defaultSession.clearData();
  141. return;
  142. }
  143. switch(args[1]){
  144. case "cache":
  145. session.defaultSession.clearCache();
  146. return;
  147. case "dns":
  148. session.defaultSession.clearHostResolverCache();
  149. return;
  150. case "storage":
  151. session.defaultSession.clearStorageData();
  152. return;
  153. default:
  154. try {
  155. let opts = JSON.parse(args.slice(1).join(""));
  156. session.defaultSession.clearData(opts);
  157. }catch(e){console.log(e)}
  158. }
  159. return;
  160. case "ext":
  161. session.defaultSession.loadExtension(args[1]);
  162. return;
  163. case "nc":
  164. bForwardCookie = false;
  165. msgbox_info("Cookie forwarding disabled");
  166. return;
  167. case "uc":
  168. if(bForwardCookie) {
  169. msgbox_info("Cookie forwarding enabled for global redirection");
  170. return;
  171. }
  172. forwardCookie();
  173. return;
  174. case "nh":
  175. bHistory = false; return;
  176. case "uh":
  177. bHistory = true; return;
  178. case "nj":
  179. bJS = false; return;
  180. case "uj":
  181. bJS = true; return;
  182. case "np":
  183. session.defaultSession.setProxy ({mode:"direct"});
  184. return;
  185. case "up":
  186. if(args.length>1)
  187. proxy = proxies[args[1]]; //retrieve proxy
  188. if(proxy)
  189. session.defaultSession.setProxy(proxy);
  190. bRedirect = false;
  191. return;
  192. case "nr":
  193. bRedirect = false; return;
  194. case "ur":
  195. bRedirect = true; return;
  196. case "ua":
  197. if(args.length==2)
  198. session.defaultSession.setUserAgent(useragents[args[1]]);
  199. else
  200. session.defaultSession.setUserAgent(defaultUA);
  201. return;
  202. }
  203. }
  204. }
  205. function cbConsoleMsg(e, level, msg, line, sourceid){
  206. console.log(line);
  207. console.log(sourceid);
  208. console.log(msg);
  209. }
  210. function cbFinishLoad(webContents){
  211. if(!bHistory) return;
  212. let histItem = webContents.getTitle()+" "+webContents.getURL()+"\n";
  213. fs.appendFile(historyFile, histItem, (err) => {});
  214. }
  215. function cbFocus(webContents){
  216. let js = "if(focusMesg){let m=focusMesg;focusMesg=null;m}";
  217. win.webContents.executeJavaScript(js,false).then((r)=>{
  218. //focusMesg as js code
  219. console.log(r);
  220. if(r) webContents.executeJavaScript(r,false);
  221. });
  222. }
  223. function interceptRequest(details, callback){
  224. if(!bJS && details.url.endsWith(".js")){
  225. callback({ cancel: true });
  226. return;
  227. }
  228. do {
  229. if(gredirect || !bRedirect ||(details.resourceType !== 'mainFrame' &&
  230. details.resourceType !== 'subFrame')) break;
  231. let oURL = new URL(details.url);
  232. let domain = oURL.hostname;
  233. let newUrl;
  234. try{
  235. let newDomain = redirects[domain];
  236. if(!newDomain) break;
  237. newUrl = "https://"+newDomain+oURL.pathname+oURL.search+oURL.hash;
  238. }catch(e){break;}
  239. callback({ cancel: false, redirectURL: newUrl });
  240. return;
  241. }while(false);
  242. callback({ cancel: false });
  243. }
  244. function cbWindowOpenHandler(details){
  245. let url = details.url;
  246. let js = "newTab();tabs.children[tabs.children.length-1].src='"+
  247. url+"';";
  248. switch(details.disposition){
  249. case "foreground-tab":
  250. case "new-window":
  251. js = js + "switchTab(tabs.children.length-1)";
  252. }
  253. win.webContents.executeJavaScript(js,false);
  254. return { action: "deny" };
  255. }
  256. function cbTitleUpdate(event,title){
  257. win.setTitle(title);
  258. }
  259. function menuArray(labelprefix, linkUrl){
  260. const menuTemplate = [
  261. {
  262. label: labelprefix+'Open Link',
  263. click: () => {
  264. shell.openExternal(linkUrl);
  265. }
  266. },
  267. {
  268. label: labelprefix+'Copy Link',
  269. click: () => {
  270. clipboard.writeText(linkUrl);
  271. }
  272. },
  273. {
  274. label: labelprefix+'Download',
  275. click: () => {
  276. win.contentView.children[i].webContents.downloadURL(linkUrl);
  277. }
  278. },
  279. ];
  280. return menuTemplate;
  281. }
  282. function onContextMenu(event, params){
  283. let url = params.linkURL;
  284. let mTemplate = [];
  285. if (url) {
  286. mTemplate.push({label:url,enabled:false});
  287. mTemplate.push.apply(mTemplate,menuArray("",url));
  288. if((url=params.srcURL))
  289. mTemplate.push.apply(mTemplate,menuArray("src: ",url));
  290. }else if((url=params.srcURL)){
  291. mTemplate.push({label:url,enabled:false});
  292. mTemplate.push.apply(mTemplate,menuArray("src: ",url));
  293. }else
  294. return;
  295. const contextMenu = Menu.buildFromTemplate(mTemplate);
  296. contextMenu.popup();
  297. }
  298. function topMenu(){
  299. const menuTemplate = [
  300. {
  301. label: '',
  302. submenu: [
  303. { label: 'Stop', accelerator: 'Ctrl+C', click: ()=>{
  304. let js="tabs.children[iTab].stop()"
  305. win.webContents.executeJavaScript(js,false)
  306. }},
  307. { label: 'getURL', accelerator: 'Ctrl+G', click: ()=>{
  308. let js="{let q=document.forms[0].q;q.focus();q.value=tabs.children[iTab].src}"
  309. win.webContents.executeJavaScript(js,false)
  310. }},
  311. { label: 'Select', accelerator: 'Ctrl+L', click:()=>{
  312. win.webContents.executeJavaScript("document.forms[0].q.select()",false);
  313. }},
  314. { label: 'New Tab', accelerator: 'Ctrl+T', click:()=>{
  315. let js = "newTab();document.forms[0].q.select();switchTab(tabs.children.length-1)";
  316. win.webContents.executeJavaScript(js,false);
  317. }},
  318. { label: 'Restore Tab', accelerator: 'Ctrl+Shift+T', click:()=>{
  319. let js = "{let u=closedUrls.pop();if(u){newTab();switchTab(tabs.children.length-1);tabs.children[iTab].src=u}}";
  320. win.webContents.executeJavaScript(js,false);
  321. }},
  322. { label: 'No redirect', accelerator: 'Ctrl+R', click: ()=>{
  323. if(gredirect){
  324. gredirect=null;
  325. unregisterHandler();
  326. }
  327. }},
  328. { label: 'Redirect', accelerator: 'Ctrl+Shift+R', click: ()=>{
  329. if(0==gredirects.length) return;
  330. if(!gredirect) registerHandler();
  331. gredirect=gredirects[0];
  332. }},
  333. { label: 'Close', accelerator: 'Ctrl+W', click: ()=>{
  334. win.webContents.executeJavaScript("tabClose()",false).then((r)=>{
  335. if(""===r) win.close();
  336. else win.setTitle(r);
  337. });
  338. }},
  339. { label: 'Next Tab', accelerator: 'Ctrl+Tab', click: ()=>{
  340. let js="tabInc(1);getWinTitle()";
  341. win.webContents.executeJavaScript(js,false).then((r)=>{
  342. win.setTitle(r);
  343. });
  344. }},
  345. { label: 'Previous Tab', accelerator: 'Ctrl+Shift+Tab', click: ()=>{
  346. let js="tabDec(-1);getWinTitle()";
  347. win.webContents.executeJavaScript(js,false).then((r)=>{
  348. win.setTitle(r);
  349. });
  350. }},
  351. { label: 'Go backward', accelerator: 'Ctrl+Left', click: ()=>{
  352. let js="tabs.children[iTab].goBack()";
  353. win.webContents.executeJavaScript(js,false);
  354. }},
  355. { label: 'Go forward', accelerator: 'Ctrl+Right', click: ()=>{
  356. let js="tabs.children[iTab].goForward()";
  357. win.webContents.executeJavaScript(js,false);
  358. }},
  359. { label: 'No focus', accelerator: 'Esc', click: ()=>{
  360. let js = `{let e=document.activeElement;
  361. if(e)e.blur();try{tabs.children[iTab].stopFindInPage('clearSelection')}catch(er){}}`;
  362. win.webContents.executeJavaScript(js,false);
  363. }},
  364. { label: 'Reload', accelerator: 'F5', click: ()=>{
  365. win.webContents.executeJavaScript("tabs.children[iTab].reload()",false);
  366. }},
  367. { label: 'Devtools', accelerator: 'F12', click: ()=>{
  368. let js = "try{tabs.children[iTab].openDevTools()}catch(e){console.log(e)}";
  369. win.webContents.executeJavaScript(js,false);
  370. }},
  371. ],
  372. },
  373. ];
  374. const menu = Menu.buildFromTemplate(menuTemplate);
  375. Menu.setApplicationMenu(menu);
  376. }
  377. function cmdlineProcess(argv,cwd,extra){
  378. let i1st = 2+extra; //index for the first query item
  379. if(argv.length>i1st){
  380. if(i1st+1==argv.length){//local file
  381. let fname = path.join(cwd, argv[i1st]);
  382. if(fs.existsSync(fname)){
  383. let js = "tabs.children[iTab].src='file://"+fname+"'";
  384. win.webContents.executeJavaScript(js,false);
  385. win.setTitle(argv[i1st]);
  386. return;
  387. }
  388. }
  389. let url=argv.slice(i1st).join(" ");
  390. win.webContents.executeJavaScript("handleQuery(`"+url+"`)",false);
  391. win.setTitle(url);
  392. }
  393. }
  394. async function cbScheme_redir(req){
  395. if(!gredirect) return null;
  396. let oUrl = req.url;
  397. let newurl = gredirect+oUrl;
  398. let options = {
  399. body: req.body,
  400. headers: req.headers,
  401. method: req.method,
  402. referer: req.referer,
  403. duplex: "half",
  404. bypassCustomProtocolHandlers: true
  405. };
  406. if(bForwardCookie){
  407. let cookies = await session.defaultSession.cookies.get({url: oUrl});
  408. let cookieS = cookies.map (cookie => cookie.name + '=' + cookie.value ).join(';');
  409. options.headers['Cookie'] = cookieS;
  410. }
  411. return fetch(newurl, options);
  412. }
  413. function registerHandler(){
  414. protocol.handle("http",cbScheme_redir);
  415. protocol.handle("https",cbScheme_redir);
  416. protocol.handle("ws",cbScheme_redir);
  417. protocol.handle("wss",cbScheme_redir);
  418. }
  419. function unregisterHandler(){
  420. protocol.unhandle("http",cbScheme_redir);
  421. protocol.unhandle("https",cbScheme_redir);
  422. protocol.unhandle("ws",cbScheme_redir);
  423. protocol.unhandle("wss",cbScheme_redir);
  424. }
  425. function forwardCookie(){
  426. const choice = dialog.showMessageBoxSync(null, {
  427. type: 'warning',
  428. title: 'Confirm cookie forwarding with global redirection',
  429. message: 'Cookies are used to access your account. Forwarding cookies is vulnerable to global redirection server, proceed to enable cookie forwarding with global redirection?',
  430. buttons: ['No','Yes']
  431. })
  432. if(1===choice) bForwardCookie=true;
  433. }
  434. function msgbox_info(msg){
  435. dialog.showMessageBoxSync(null, {
  436. type: 'info',
  437. title: msg,
  438. message: msg,
  439. buttons: ['OK']
  440. })
  441. }