uweb/misc/ebrowser/webview.js
2024-06-15 11:36:06 +08:00

474 lines
13 KiB
JavaScript

/* Copyright (C) 2024 Richard Hao Cao
*/
const {
app, BrowserWindow, Menu, shell, clipboard,
session, protocol, net, dialog
} = require('electron')
let win;
if(!app.requestSingleInstanceLock())
app.quit()
else {
app.on('ready', createWindow);
app.on('second-instance', (event, args, cwd) => {
if (win) {
if (win.isMinimized()) {
win.restore()
}
win.show()
win.focus()
cmdlineProcess(args,cwd,1);
}else
createWindow();
})
}
topMenu();
const fs = require('fs');
const readline = require('readline');
const path = require('path')
const process = require('process')
var gredirects = [];
var gredirect;
var redirects;
var bRedirect = true;
var bJS = true;
var bHistory = false;
var bForwardCookie = false;
var proxies = {};
var proxy;
var useragents = {};
var defaultUA =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" +
process.versions.chrome +" Safari/537.36";
app.userAgentFallback = defaultUA;
var historyFile = path.join(__dirname,'history.rec');
fs.readFile(path.join(__dirname,'redirect.json'), 'utf8', (err, jsonString) => {
if (err) return;
try {
redirects = JSON.parse(jsonString);
} catch (e){console.log(e)}
});
async function createWindow () {
let json = await fs.promises.readFile(path.join(__dirname,'uas.json'), 'utf8');
try {
useragents = JSON.parse(json);
} catch (e){console.log(e)}
await (async ()=>{
try{
const readInterface = readline.createInterface ({
input: fs.createReadStream (path.join(__dirname,'config'), 'utf8'),
});
for await (const line of readInterface) {
addrCommand(line);
}
}catch(e){console.log(e);}
})();
win = new BrowserWindow(
{width: 800, height: 600,autoHideMenuBar: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
webviewTag: true,
}});
win.setMenuBarVisibility(false);
win.on('closed', function () {
win = null
})
win.loadFile('index.html');
fs.readFile(path.join(__dirname,'gredirect.json'), 'utf8', (err, jsonString) => {
if (err) return;
try {
gredirects = JSON.parse(jsonString);
} catch (e){console.log(e)}
});
fs.readFile(path.join(__dirname,'proxy.json'), 'utf8', (err, jsonString) => {
if (err) return;
try {
proxies = JSON.parse(jsonString, (key,val)=>{
if(!proxy && key==="proxyRules"){
proxy = {proxyRules:val};
}
return val;
});
} catch (e){console.log(e)}
});
cmdlineProcess(process.argv, process.cwd(), 0);
//app.commandLine.appendSwitch ('trace-warnings');
win.webContents.on('page-title-updated',(event,cmd)=>{
addrCommand(cmd);
});
win.webContents.on('console-message',cbConsoleMsg);
}
app.on('window-all-closed', function () {
app.quit()
})
app.on('activate', function () {
if (win === null) {
createWindow()
}
})
app.on('will-quit', () => {
})
app.on ('web-contents-created', (event, contents) => {
if (contents.getType () === 'webview') {
contents.setWindowOpenHandler(cbWindowOpenHandler);
contents.on('context-menu',onContextMenu);
contents.on('page-title-updated',cbTitleUpdate);
//contents.on('console-message',cbConsoleMsg);
//contents.on('focus', ()=>{cbFocus(contents)});
//contents.on('blur',()=>{cbBlur()});
contents.session.webRequest.onBeforeRequest(interceptRequest);
contents.on('did-finish-load',()=>{cbFinishLoad(contents)});
}
});
function addrCommand(cmd){
if(cmd.length<3) return;
let c0 = cmd.charCodeAt(0);
switch(c0){
case 58: //':'
args = cmd.substring(1).split(/\s+/);
switch(args[0]){
case "cert":
if(args.length==1)
session.defaultSession.setCertificateVerifyProc((request, callback) => {
callback(0);
});
else
session.defaultSession.setCertificateVerifyProc(null);
return;
case "clear":
if(args.length==1){
session.defaultSession.clearData();
return;
}
switch(args[1]){
case "cache":
session.defaultSession.clearCache();
return;
case "dns":
session.defaultSession.clearHostResolverCache();
return;
case "storage":
session.defaultSession.clearStorageData();
return;
default:
try {
let opts = JSON.parse(args.slice(1).join(""));
session.defaultSession.clearData(opts);
}catch(e){console.log(e)}
}
return;
case "ext":
session.defaultSession.loadExtension(args[1]);
return;
case "nc":
bForwardCookie = false;
msgbox_info("Cookie forwarding disabled");
return;
case "uc":
if(bForwardCookie) {
msgbox_info("Cookie forwarding enabled for global redirection");
return;
}
forwardCookie();
return;
case "nh":
bHistory = false; return;
case "uh":
bHistory = true; return;
case "nj":
bJS = false; return;
case "uj":
bJS = true; return;
case "np":
session.defaultSession.setProxy ({mode:"direct"});
return;
case "up":
if(args.length>1)
proxy = proxies[args[1]]; //retrieve proxy
if(proxy)
session.defaultSession.setProxy(proxy);
bRedirect = false;
return;
case "nr":
bRedirect = false; return;
case "ur":
bRedirect = true; return;
case "ua":
if(args.length==2)
session.defaultSession.setUserAgent(useragents[args[1]]);
else
session.defaultSession.setUserAgent(defaultUA);
return;
}
}
}
function cbConsoleMsg(e, level, msg, line, sourceid){
console.log(line);
console.log(sourceid);
console.log(msg);
}
function cbFinishLoad(webContents){
if(!bHistory) return;
let histItem = webContents.getTitle()+" "+webContents.getURL()+"\n";
fs.appendFile(historyFile, histItem, (err) => {});
}
function cbFocus(webContents){
let js = "if(focusMesg){let m=focusMesg;focusMesg=null;m}";
win.webContents.executeJavaScript(js,false).then((r)=>{
//focusMesg as js code
console.log(r);
if(r) webContents.executeJavaScript(r,false);
});
}
function interceptRequest(details, callback){
if(!bJS && details.url.endsWith(".js")){
callback({ cancel: true });
return;
}
do {
if(gredirect || !bRedirect ||(details.resourceType !== 'mainFrame' &&
details.resourceType !== 'subFrame')) break;
let oURL = new URL(details.url);
let domain = oURL.hostname;
let newUrl;
try{
let newDomain = redirects[domain];
if(!newDomain) break;
newUrl = "https://"+newDomain+oURL.pathname+oURL.search+oURL.hash;
}catch(e){break;}
callback({ cancel: false, redirectURL: newUrl });
return;
}while(false);
callback({ cancel: false });
}
function cbWindowOpenHandler(details){
let url = details.url;
let js = "newTab();tabs.children[tabs.children.length-1].src='"+
url+"';";
switch(details.disposition){
case "foreground-tab":
case "new-window":
js = js + "switchTab(tabs.children.length-1)";
}
win.webContents.executeJavaScript(js,false);
return { action: "deny" };
}
function cbTitleUpdate(event,title){
win.setTitle(title);
}
function menuArray(labelprefix, linkUrl){
const menuTemplate = [
{
label: labelprefix+'Open Link',
click: () => {
shell.openExternal(linkUrl);
}
},
{
label: labelprefix+'Copy Link',
click: () => {
clipboard.writeText(linkUrl);
}
},
{
label: labelprefix+'Download',
click: () => {
win.contentView.children[i].webContents.downloadURL(linkUrl);
}
},
];
return menuTemplate;
}
function onContextMenu(event, params){
let url = params.linkURL;
let mTemplate = [];
if (url) {
mTemplate.push({label:url,enabled:false});
mTemplate.push.apply(mTemplate,menuArray("",url));
if((url=params.srcURL))
mTemplate.push.apply(mTemplate,menuArray("src: ",url));
}else if((url=params.srcURL)){
mTemplate.push({label:url,enabled:false});
mTemplate.push.apply(mTemplate,menuArray("src: ",url));
}else
return;
const contextMenu = Menu.buildFromTemplate(mTemplate);
contextMenu.popup();
}
function topMenu(){
const menuTemplate = [
{
label: '',
submenu: [
{ label: 'Stop', accelerator: 'Ctrl+C', click: ()=>{
let js="tabs.children[iTab].stop()"
win.webContents.executeJavaScript(js,false)
}},
{ label: 'getURL', accelerator: 'Ctrl+G', click: ()=>{
let js="{let q=document.forms[0].q;q.focus();q.value=tabs.children[iTab].src}"
win.webContents.executeJavaScript(js,false)
}},
{ label: 'Select', accelerator: 'Ctrl+L', click:()=>{
win.webContents.executeJavaScript("document.forms[0].q.select()",false);
}},
{ label: 'New Tab', accelerator: 'Ctrl+T', click:()=>{
let js = "newTab();document.forms[0].q.select();switchTab(tabs.children.length-1)";
win.webContents.executeJavaScript(js,false);
}},
{ label: 'Restore Tab', accelerator: 'Ctrl+Shift+T', click:()=>{
let js = "{let u=closedUrls.pop();if(u){newTab();switchTab(tabs.children.length-1);tabs.children[iTab].src=u}}";
win.webContents.executeJavaScript(js,false);
}},
{ label: 'No redirect', accelerator: 'Ctrl+R', click: ()=>{
if(gredirect){
gredirect=null;
unregisterHandler();
}
}},
{ label: 'Redirect', accelerator: 'Ctrl+Shift+R', click: ()=>{
if(0==gredirects.length) return;
if(!gredirect) registerHandler();
gredirect=gredirects[0];
}},
{ label: 'Close', accelerator: 'Ctrl+W', click: ()=>{
win.webContents.executeJavaScript("tabClose()",false).then((r)=>{
if(""===r) win.close();
else win.setTitle(r);
});
}},
{ label: 'Next Tab', accelerator: 'Ctrl+Tab', click: ()=>{
let js="tabInc(1);getWinTitle()";
win.webContents.executeJavaScript(js,false).then((r)=>{
win.setTitle(r);
});
}},
{ label: 'Previous Tab', accelerator: 'Ctrl+Shift+Tab', click: ()=>{
let js="tabDec(-1);getWinTitle()";
win.webContents.executeJavaScript(js,false).then((r)=>{
win.setTitle(r);
});
}},
{ label: 'Go backward', accelerator: 'Ctrl+Left', click: ()=>{
let js="tabs.children[iTab].goBack()";
win.webContents.executeJavaScript(js,false);
}},
{ label: 'Go forward', accelerator: 'Ctrl+Right', click: ()=>{
let js="tabs.children[iTab].goForward()";
win.webContents.executeJavaScript(js,false);
}},
{ label: 'No focus', accelerator: 'Esc', click: ()=>{
let js = `{let e=document.activeElement;
if(e)e.blur();try{tabs.children[iTab].stopFindInPage('clearSelection')}catch(er){}}`;
win.webContents.executeJavaScript(js,false);
}},
{ label: 'Reload', accelerator: 'F5', click: ()=>{
win.webContents.executeJavaScript("tabs.children[iTab].reload()",false);
}},
{ label: 'Devtools', accelerator: 'F12', click: ()=>{
let js = "try{tabs.children[iTab].openDevTools()}catch(e){console.log(e)}";
win.webContents.executeJavaScript(js,false);
}},
],
},
];
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
}
function cmdlineProcess(argv,cwd,extra){
let i1st = 2+extra; //index for the first query item
if(argv.length>i1st){
if(i1st+1==argv.length){//local file
let fname = path.join(cwd, argv[i1st]);
if(fs.existsSync(fname)){
let js = "tabs.children[iTab].src='file://"+fname+"'";
win.webContents.executeJavaScript(js,false);
win.setTitle(argv[i1st]);
return;
}
}
let url=argv.slice(i1st).join(" ");
win.webContents.executeJavaScript("handleQuery(`"+url+"`)",false);
win.setTitle(url);
}
}
async function cbScheme_redir(req){
if(!gredirect) return null;
let oUrl = req.url;
let newurl = gredirect+oUrl;
let options = {
body: req.body,
headers: req.headers,
method: req.method,
referer: req.referer,
duplex: "half",
bypassCustomProtocolHandlers: true
};
if(bForwardCookie){
let cookies = await session.defaultSession.cookies.get({url: oUrl});
let cookieS = cookies.map (cookie => cookie.name + '=' + cookie.value ).join(';');
options.headers['Cookie'] = cookieS;
}
return fetch(newurl, options);
}
function registerHandler(){
protocol.handle("http",cbScheme_redir);
protocol.handle("https",cbScheme_redir);
protocol.handle("ws",cbScheme_redir);
protocol.handle("wss",cbScheme_redir);
}
function unregisterHandler(){
protocol.unhandle("http",cbScheme_redir);
protocol.unhandle("https",cbScheme_redir);
protocol.unhandle("ws",cbScheme_redir);
protocol.unhandle("wss",cbScheme_redir);
}
function forwardCookie(){
const choice = dialog.showMessageBoxSync(null, {
type: 'warning',
title: 'Confirm cookie forwarding with global redirection',
message: 'Cookies are used to access your account. Forwarding cookies is vulnerable to global redirection server, proceed to enable cookie forwarding with global redirection?',
buttons: ['No','Yes']
})
if(1===choice) bForwardCookie=true;
}
function msgbox_info(msg){
dialog.showMessageBoxSync(null, {
type: 'info',
title: msg,
message: msg,
buttons: ['OK']
})
}