adaptable ebrowser links
[uweb.git] / misc / ebrowser / webview.js
blobf1f8099ef288a4ec23ed8ee5bdc0fc2cfd37d462
1 const {
2 app, BrowserWindow, Menu, shell, clipboard,
3 session, protocol, net} = require('electron')
4 let win;
6 if(!app.requestSingleInstanceLock())
7 app.quit()
8 else {
9 app.on('ready', createWindow);
10 app.on('second-instance', (event, args, cwd) => {
11 // 当已经有运行的实例时,我们激活窗口而不是创建新的窗口
12 if (win) {
13 if (win.isMinimized()) {
14 win.restore()
16 win.show()
17 win.focus()
18 cmdlineProcess(args,cwd,1);
19 }else
20 createWindow();
23 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 proxies = {};
36 var proxy;
37 var useragents = {};
38 var defaultUA =
39 "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" +
40 process.versions.chrome +" Safari/537.36";
41 app.userAgentFallback = defaultUA;
42 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){}
49 });
51 async function createWindow () {
52 let json = await fs.promises.readFile(path.join(__dirname,'uas.json'), 'utf8');
53 try {
54 useragents = JSON.parse(json);
55 } catch (e){}
57 await (async ()=>{
58 try{
59 const readInterface = readline.createInterface ({
60 input: fs.createReadStream (path.join(__dirname,'config'), 'utf8'),
61 });
63 for await (const line of readInterface) {
64 addrCommand(line);
66 }catch(e){return;}
67 })();
69 win = new BrowserWindow(
70 {width: 800, height: 600,autoHideMenuBar: true,
71 webPreferences: {
72 nodeIntegration: true,
73 contextIsolation: false,
74 webviewTag: true,
75 }});
76 win.setMenuBarVisibility(false);
77 win.on('closed', function () {
78 win = null
81 win.loadFile('index.html');
82 fs.readFile(path.join(__dirname,'gredirect.json'), 'utf8', (err, jsonString) => {
83 if (err) return;
84 try {
85 gredirects = JSON.parse(jsonString);
86 } catch (e){}
87 });
89 fs.readFile(path.join(__dirname,'proxy.json'), 'utf8', (err, jsonString) => {
90 if (err) return;
91 try {
92 proxies = JSON.parse(jsonString, (key,val)=>{
93 if(!proxy && key==="proxyRules"){
94 proxy = {proxyRules:val};
96 return val;
97 });
98 } catch (e){}
99 });
101 cmdlineProcess(process.argv, process.cwd(), 0);
102 //app.commandLine.appendSwitch ('trace-warnings');
104 win.webContents.on('page-title-updated',(event,cmd)=>{
105 addrCommand(cmd);
108 win.webContents.on('console-message',cbConsoleMsg);
109 //protocol.handle("https",cbScheme_https);
112 app.on('window-all-closed', function () {
113 app.quit()
116 app.on('activate', function () {
117 if (win === null) {
118 createWindow()
122 app.on('will-quit', () => {
125 app.on ('web-contents-created', (event, contents) => {
126 if (contents.getType () === 'webview') {
127 contents.setWindowOpenHandler(cbWindowOpenHandler);
128 contents.on('context-menu',onContextMenu);
129 contents.on('page-title-updated',cbTitleUpdate);
130 //contents.on('console-message',cbConsoleMsg);
131 //contents.on('focus', ()=>{cbFocus(contents)});
132 //contents.on('blur',()=>{cbBlur()});
133 contents.session.webRequest.onBeforeRequest(interceptRequest);
134 contents.on('did-finish-load',()=>{cbFinishLoad(contents)});
138 function addrCommand(cmd){
139 if(cmd.length<3) return;
140 let c0 = cmd.charCodeAt(0);
141 switch(c0){
142 case 58: //':'
143 args = cmd.substring(1).split(/\s+/);
144 switch(args[0]){
145 case "cert":
146 if(args.length==1)
147 session.defaultSession.setCertificateVerifyProc((request, callback) => {
148 callback(0);
150 else
151 session.defaultSession.setCertificateVerifyProc(null);
152 return;
153 case "clear":
154 if(args.length==1){
155 return;
157 switch(args[1]){
158 case "cache":
159 session.defaultSession.clearCache();
160 return;
161 case "dns":
162 session.defaultSession.clearHostResolverCache();
163 return;
164 case "storage":
165 session.defaultSession.clearStorageData();
166 return;
168 return;
169 case "ext":
170 session.defaultSession.loadExtension(args[1]);
171 return;
172 case "nh":
173 bHistory = false; return;
174 case "uh":
175 bHistory = true; return;
176 case "nj":
177 bJS = false; return;
178 case "uj":
179 bJS = true; return;
180 case "np":
181 session.defaultSession.setProxy ({mode:"direct"});
182 return;
183 case "up":
184 if(args.length>1)
185 proxy = proxies[args[1]]; //retrieve proxy
186 if(proxy)
187 session.defaultSession.setProxy(proxy);
188 bRedirect = false;
189 return;
190 case "nr":
191 bRedirect = false; return;
192 case "ur":
193 bRedirect = true; return;
194 case "ua":
195 if(args.length==2)
196 session.defaultSession.setUserAgent(useragents[args[1]]);
197 else
198 session.defaultSession.setUserAgent(defaultUA);
199 return;
204 function cbConsoleMsg(e, level, msg, line, sourceid){
205 console.log(line);
206 console.log(sourceid);
207 console.log(msg);
210 function cbFinishLoad(webContents){
211 if(!bHistory) return;
212 let histItem = webContents.getTitle()+" "+webContents.getURL()+"\n";
213 fs.appendFile(historyFile, histItem, (err) => {});
216 function cbFocus(webContents){
217 let js = "if(focusMesg){let m=focusMesg;focusMesg=null;m}";
218 win.webContents.executeJavaScript(js,false).then((r)=>{
219 //focusMesg as js code
220 console.log(r);
221 if(r) webContents.executeJavaScript(r,false);
225 function interceptRequest(details, callback){
226 if(!bJS && details.url.endsWith(".js")){
227 callback({ cancel: true });
228 return;
230 do {
231 if(gredirect){
232 if(!details.url.startsWith("http")) break;
233 if(!details.url.startsWith(gredirect)){
234 if(details.resourceType === 'mainFrame'){
235 let wc = details.webContents;
236 let url = details.url;
237 if(wc){
238 let nUrl = gredirect+url;
239 fetch(nUrl).then(res=>{
240 if(res.ok) return res.arrayBuffer();
241 throw new Error(`Err: ${res.status} - ${res.statusText}`);
242 }).then(aBuf=>{
243 const u8 = new Uint8Array(aBuf);
244 const b64 = Buffer.from (u8).toString('base64');
245 const dataUrl = `data:text/html;base64,${b64}`;
246 wc.loadURL(dataUrl,{baseURLForDataURL:url});
247 }).catch(e=>{
248 console.log(nUrl+" err:",e);
250 callback({ cancel: true });
251 return;
254 let newUrl = gredirect + details.url;
255 callback({ cancel: false, redirectURL: newUrl });
256 return;
258 break;
261 if(!bRedirect ||(details.resourceType !== 'mainFrame' &&
262 details.resourceType !== 'subFrame')) break;
263 let oURL = new URL(details.url);
264 let domain = oURL.hostname;
265 let newUrl;
266 try{
267 let newDomain = redirects[domain];
268 if(!newDomain) break;
269 newUrl = "https://"+newDomain+oURL.pathname+oURL.search+oURL.hash;
270 }catch(e){break;}
271 callback({ cancel: false, redirectURL: newUrl });
272 return;
273 }while(false);
274 callback({ cancel: false });
277 function cbWindowOpenHandler(details){
278 let url = details.url;
279 let js = "newTab();tabs.children[tabs.children.length-1].src='"+
280 url+"';";
281 switch(details.disposition){
282 case "foreground-tab":
283 case "new-window":
284 js = js + "switchTab(tabs.children.length-1)";
286 win.webContents.executeJavaScript(js,false);
287 return { action: "deny" };
289 function cbTitleUpdate(event,title){
290 win.setTitle(title);
292 function menuArray(labelprefix, linkUrl){
293 const menuTemplate = [
295 label: labelprefix+'Open Link',
296 click: () => {
297 shell.openExternal(linkUrl);
301 label: labelprefix+'Copy Link',
302 click: () => {
303 clipboard.writeText(linkUrl);
307 label: labelprefix+'Download',
308 click: () => {
309 win.contentView.children[i].webContents.downloadURL(linkUrl);
313 return menuTemplate;
316 function onContextMenu(event, params){
317 let url = params.linkURL;
318 let mTemplate = [];
319 if (url) {
320 mTemplate.push({label:url,enabled:false});
321 mTemplate.push.apply(mTemplate,menuArray("",url));
322 if((url=params.srcURL))
323 mTemplate.push.apply(mTemplate,menuArray("src: ",url));
324 }else if((url=params.srcURL)){
325 mTemplate.push({label:url,enabled:false});
326 mTemplate.push.apply(mTemplate,menuArray("src: ",url));
327 }else
328 return;
330 const contextMenu = Menu.buildFromTemplate(mTemplate);
331 contextMenu.popup();
334 function topMenu(){
335 const menuTemplate = [
337 label: '',
338 submenu: [
339 { label: '', accelerator: 'Ctrl+G', click: ()=>{
340 let js="{let q=document.forms[0].q;q.focus();q.value=tabs.children[iTab].src}"
341 win.webContents.executeJavaScript(js,false)
343 { label: '', accelerator: 'Ctrl+L', click:()=>{
344 win.webContents.executeJavaScript("document.forms[0].q.select()",false);
346 { label: '', accelerator: 'Ctrl+T', click:()=>{
347 let js = "newTab();document.forms[0].q.select();switchTab(tabs.children.length-1)";
348 win.webContents.executeJavaScript(js,false);
350 { label: '', accelerator: 'Ctrl+R', click: ()=>{
351 gredirect=null;
353 { label: '', accelerator: 'Ctrl+Shift+R', click: ()=>{
354 if(0==gredirects.length) return;
355 gredirect=gredirects[0];
357 { label: '', accelerator: 'Ctrl+W', click: ()=>{
358 win.webContents.executeJavaScript("tabClose()",false).then((r)=>{
359 if(""===r) win.close();
360 else win.setTitle(r);
363 { label: '', accelerator: 'Ctrl+Tab', click: ()=>{
364 let js="tabInc(1);getWinTitle()";
365 win.webContents.executeJavaScript(js,false).then((r)=>{
366 win.setTitle(r);
369 { label: '', accelerator: 'Ctrl+Shift+Tab', click: ()=>{
370 let js="tabDec(-1);getWinTitle()";
371 win.webContents.executeJavaScript(js,false).then((r)=>{
372 win.setTitle(r);
375 { label: '', accelerator: 'Ctrl+Left', click: ()=>{
376 let js="tabs.children[iTab].goBack()";
377 win.webContents.executeJavaScript(js,false);
379 { label: '', accelerator: 'Ctrl+Right', click: ()=>{
380 let js="tabs.children[iTab].goForward()";
381 win.webContents.executeJavaScript(js,false);
383 { label: '', accelerator: 'Esc', click: ()=>{
384 let js = `{let e=document.activeElement;
385 if(e)e.blur();try{tabs.children[iTab].stopFindInPage('clearSelection')}catch(er){}}`;
386 win.webContents.executeJavaScript(js,false);
388 { label: '', accelerator: 'F5', click: ()=>{
389 win.webContents.executeJavaScript("tabs.children[iTab].reload()",false);
391 { label: '', accelerator: 'F12', click: ()=>{
392 let js = "try{tabs.children[iTab].openDevTools()}catch(e){console.log(e)}";
393 win.webContents.executeJavaScript(js,false);
399 const menu = Menu.buildFromTemplate(menuTemplate);
400 Menu.setApplicationMenu(menu);
403 function cmdlineProcess(argv,cwd,extra){
404 let i1st = 2+extra; //index for the first query item
405 if(argv.length>i1st){
406 if(i1st+1==argv.length){//local file
407 let fname = path.join(cwd, argv[i1st]);
408 if(fs.existsSync(fname)){
409 let js = "tabs.children[iTab].src='file://"+fname+"'";
410 win.webContents.executeJavaScript(js,false);
411 win.setTitle(argv[i1st]);
412 return;
415 let url=argv.slice(i1st).join(" ");
416 win.webContents.executeJavaScript("handleQuery(`"+url+"`)",false);
417 win.setTitle(url);