2024-06-13 00:07:31 +00:00
/ * C o p y r i g h t ( C ) 2 0 2 4 R i c h a r d H a o C a o
* /
2024-06-11 10:42:38 +00:00
const {
app , BrowserWindow , Menu , shell , clipboard ,
2024-06-15 03:36:06 +00:00
session , protocol , net , dialog
} = require ( 'electron' )
2024-06-11 10:42:38 +00:00
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 ;
2024-06-15 03:36:06 +00:00
var bForwardCookie = false ;
2024-06-11 10:42:38 +00:00
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 ) ;
2024-06-15 03:36:06 +00:00
} catch ( e ) { console . log ( e ) }
2024-06-11 10:42:38 +00:00
} ) ;
async function createWindow ( ) {
let json = await fs . promises . readFile ( path . join ( _ _dirname , 'uas.json' ) , 'utf8' ) ;
try {
useragents = JSON . parse ( json ) ;
2024-06-15 03:36:06 +00:00
} catch ( e ) { console . log ( e ) }
2024-06-11 10:42:38 +00:00
await ( async ( ) => {
try {
const readInterface = readline . createInterface ( {
input : fs . createReadStream ( path . join ( _ _dirname , 'config' ) , 'utf8' ) ,
} ) ;
for await ( const line of readInterface ) {
addrCommand ( line ) ;
}
2024-06-15 03:36:06 +00:00
} catch ( e ) { console . log ( e ) ; }
2024-06-11 10:42:38 +00:00
} ) ( ) ;
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 ) ;
2024-06-15 03:36:06 +00:00
} catch ( e ) { console . log ( e ) }
2024-06-11 10:42:38 +00:00
} ) ;
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 ;
} ) ;
2024-06-15 03:36:06 +00:00
} catch ( e ) { console . log ( e ) }
2024-06-11 10:42:38 +00:00
} ) ;
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 ) {
2024-06-15 03:36:06 +00:00
session . defaultSession . clearData ( ) ;
2024-06-11 10:42:38 +00:00
return ;
}
switch ( args [ 1 ] ) {
case "cache" :
session . defaultSession . clearCache ( ) ;
return ;
case "dns" :
session . defaultSession . clearHostResolverCache ( ) ;
return ;
case "storage" :
session . defaultSession . clearStorageData ( ) ;
return ;
2024-06-15 03:36:06 +00:00
default :
try {
let opts = JSON . parse ( args . slice ( 1 ) . join ( "" ) ) ;
session . defaultSession . clearData ( opts ) ;
} catch ( e ) { console . log ( e ) }
2024-06-11 10:42:38 +00:00
}
return ;
case "ext" :
session . defaultSession . loadExtension ( args [ 1 ] ) ;
return ;
2024-06-18 15:28:12 +00:00
case "gr" :
if ( args . length < 2 ) {
gredirect _enable ( 0 ) ;
return ;
}
let i = parseInt ( args [ 1 ] ) ;
if ( i >= 0 && i < gredirects . length )
gredirect _enable ( i ) ;
else
gredirect _disable ( ) ;
return ;
2024-06-15 03:36:06 +00:00
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 ;
2024-06-11 10:42:38 +00:00
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" } ) ;
2024-06-18 02:41:17 +00:00
bRedirect = true ;
2024-06-11 10:42:38 +00:00
return ;
case "up" :
if ( args . length > 1 )
proxy = proxies [ args [ 1 ] ] ; //retrieve proxy
2024-06-18 02:41:17 +00:00
if ( proxy ) {
2024-06-18 15:28:12 +00:00
gredirect _disable ( ) ;
2024-06-11 10:42:38 +00:00
session . defaultSession . setProxy ( proxy ) ;
2024-06-18 02:41:17 +00:00
}
2024-06-11 10:42:38 +00:00
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 ;
2024-06-18 15:28:12 +00:00
case "update" :
let updateurl ;
if ( 1 == args . length )
updateurl = "https://gitlab.com/jamesfengcao/uweb/-/raw/master/misc/ebrowser/" ;
else {
updateurl = args [ 1 ] ;
if ( ! updateurl . endsWith ( "/" ) ) updateurl = updateurl + "/" ;
}
updateApp ( updateurl ) ;
return ;
2024-06-11 10:42:38 +00:00
}
}
}
2024-06-18 15:28:12 +00:00
function gredirect _disable ( ) {
if ( gredirect ) {
gredirect = null ;
unregisterHandler ( ) ;
}
bRedirect = false ;
}
function gredirect _enable ( i ) {
if ( i >= gredirects . length ) return ;
if ( ! gredirect ) registerHandler ( ) ;
gredirect = gredirects [ i ] ;
}
2024-06-11 10:42:38 +00:00
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 {
2024-06-15 03:36:06 +00:00
if ( gredirect || ! bRedirect || ( details . resourceType !== 'mainFrame' &&
2024-06-11 10:42:38 +00:00
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 : [
2024-06-18 02:41:17 +00:00
{ label : 'Help' , accelerator : 'F1' , click : ( ) => {
let js = "tabs.children[iTab].src='file://'+__dirname+'/README.md'" ;
win . webContents . executeJavaScript ( js , false )
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Stop' , accelerator : 'Ctrl+C' , click : ( ) => {
let js = "tabs.children[iTab].stop()"
win . webContents . executeJavaScript ( js , false )
} } ,
{ label : 'getURL' , accelerator : 'Ctrl+G' , click : ( ) => {
2024-06-11 10:42:38 +00:00
let js = "{let q=document.forms[0].q;q.focus();q.value=tabs.children[iTab].src}"
win . webContents . executeJavaScript ( js , false )
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Select' , accelerator : 'Ctrl+L' , click : ( ) => {
2024-06-11 10:42:38 +00:00
win . webContents . executeJavaScript ( "document.forms[0].q.select()" , false ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'New Tab' , accelerator : 'Ctrl+T' , click : ( ) => {
2024-06-11 10:42:38 +00:00
let js = "newTab();document.forms[0].q.select();switchTab(tabs.children.length-1)" ;
win . webContents . executeJavaScript ( js , false ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ 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 : ( ) => {
2024-06-18 15:28:12 +00:00
gredirect _disable ( ) ;
2024-06-11 10:42:38 +00:00
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Redirect' , accelerator : 'Ctrl+Shift+R' , click : ( ) => {
2024-06-18 15:28:12 +00:00
gredirect _enable ( 0 ) ;
2024-06-11 10:42:38 +00:00
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Close' , accelerator : 'Ctrl+W' , click : ( ) => {
2024-06-11 10:42:38 +00:00
win . webContents . executeJavaScript ( "tabClose()" , false ) . then ( ( r ) => {
if ( "" === r ) win . close ( ) ;
else win . setTitle ( r ) ;
} ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Next Tab' , accelerator : 'Ctrl+Tab' , click : ( ) => {
2024-06-11 10:42:38 +00:00
let js = "tabInc(1);getWinTitle()" ;
win . webContents . executeJavaScript ( js , false ) . then ( ( r ) => {
win . setTitle ( r ) ;
} ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Previous Tab' , accelerator : 'Ctrl+Shift+Tab' , click : ( ) => {
2024-06-11 10:42:38 +00:00
let js = "tabDec(-1);getWinTitle()" ;
win . webContents . executeJavaScript ( js , false ) . then ( ( r ) => {
win . setTitle ( r ) ;
} ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Go backward' , accelerator : 'Ctrl+Left' , click : ( ) => {
2024-06-11 10:42:38 +00:00
let js = "tabs.children[iTab].goBack()" ;
win . webContents . executeJavaScript ( js , false ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Go forward' , accelerator : 'Ctrl+Right' , click : ( ) => {
2024-06-11 10:42:38 +00:00
let js = "tabs.children[iTab].goForward()" ;
win . webContents . executeJavaScript ( js , false ) ;
} } ,
2024-06-18 02:41:17 +00:00
{ label : 'Zoom in' , accelerator : 'Ctrl+Shift+=' , click : ( ) => {
let js = "{let t=tabs.children[iTab];let s=t.getZoomFactor()*1.2;t.setZoomFactor(s)}" ;
win . webContents . executeJavaScript ( js , false ) ;
} } ,
{ label : 'Zoom out' , accelerator : 'Ctrl+-' , click : ( ) => {
let js = "{let t=tabs.children[iTab];let s=t.getZoomFactor()/1.2;t.setZoomFactor(s)}" ;
win . webContents . executeJavaScript ( js , false ) ;
} } ,
{ label : 'Default zoom' , accelerator : 'Ctrl+0' , click : ( ) => {
let js = "tabs.children[iTab].setZoomFactor(1)" ;
win . webContents . executeJavaScript ( js , false ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'No focus' , accelerator : 'Esc' , click : ( ) => {
2024-06-11 10:42:38 +00:00
let js = ` {let e=document.activeElement;
if ( e ) e . blur ( ) ; try { tabs . children [ iTab ] . stopFindInPage ( 'clearSelection' ) } catch ( er ) { } } ` ;
win . webContents . executeJavaScript ( js , false ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Reload' , accelerator : 'F5' , click : ( ) => {
2024-06-11 10:42:38 +00:00
win . webContents . executeJavaScript ( "tabs.children[iTab].reload()" , false ) ;
} } ,
2024-06-15 03:36:06 +00:00
{ label : 'Devtools' , accelerator : 'F12' , click : ( ) => {
2024-06-11 10:42:38 +00:00
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 ) ;
}
}
2024-06-15 03:36:06 +00:00
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' ]
} )
}
2024-06-18 15:28:12 +00:00
async function updateApp ( url ) { //url must ending with "/"
let msg ;
do {
try {
let res = await fetch ( url + "package.json" ) ;
let packageS = await res . text ( ) ;
let nLatestVer ;
//the last part of version string is the version number, must keep increasing
{
let head = packageS . slice ( 2 , 40 ) ;
let iV = head . indexOf ( "version" ) ;
if ( iV < 0 ) {
msg = "remote package.json corrupted"
break ;
}
iV = iV + 11 ;
let iE = head . indexOf ( '"' , iV + 4 ) ;
let iS = head . lastIndexOf ( '.' , iE - 1 ) ;
nLatestVer = parseInt ( head . substring ( iS + 1 , iE ) ) ;
}
let nVer ;
{
let ver = process . versions ;
let iS = ver . lastIndexOf ( '.' ) ;
nVer = parseInt ( ver . substring ( iS + 1 ) ) ;
}
if ( nVer >= nLatestVer ) {
msg = "Already up to date" ;
break ;
}
writeFile ( "package.json" , packageS ) ;
fetch2file ( url , "webview.js" ) ;
fetch2file ( url , "index.html" ) ;
msg = "Update completed" ;
} catch ( e ) {
msg = "Fail to update"
}
} while ( false ) ;
dialog . showMessageBoxSync ( null , {
type : 'info' ,
title : msg ,
message : msg ,
buttons : [ 'OK' ]
} )
}
async function fetch2file ( urlFolder , filename ) {
let res = await fetch ( urlFolder + filename ) ;
let str = await res . text ( ) ;
writeFile ( filename , str ) ;
}
2024-06-15 03:36:06 +00:00
2024-06-18 15:28:12 +00:00
async function writeFile ( filename , str ) {
let pathname = path . join ( _ _dirname , filename + ".new" ) ;
fs . writeFile ( pathname , str , ( err ) => {
if ( err ) throw "Fail to write" ;
fs . rename ( pathname , path . join ( _ _dirname , filename ) , ( e1 ) => {
if ( e1 ) throw "Fail to rename" ;
} ) ;
} ) ;
}