作为一个前端开发人员,你可能已经听说过Electron了,你知道VS Code是基于这个技术开发的。不但VS Code,
目前一些大热的软件:飞书、Slack、WhatsApp都是基于这个技术开发的。
即使工作不会涉及到,也应该学一下Electron,因为它是极客手里的利器,你可以通过他做很多Amazing的事情。
本文主要讲讲Electron是什么,以及它能做什么极客工作。
Electron的由来
2011年左右,中国英特尔开源技术中心的王文睿(Roger Wang)希望能用Node.js来操作WebKit,而创建了node-webkit项目,这就是NW.js的前身。当时的目的并不是用这两个技术来开发桌面GUI应用。
中国英特尔开源技术中心大力支持了这个项目,不但允许王文睿分出一部分精力来做这个开源项目,还给了他招聘名额,允许他招聘其他工程师来一起完成这个项目。2012年,故事的另一个主角赵成(Cheng Zhao)加入到王文睿的小组,并对node-webkit项目做出了大量的改进。
后来赵成离开了中国英特尔开源技术中心,帮助github团队尝试把node-webkit应用到Atom编辑器上,但由于当时node-webkit还并不稳定,且node-webkit项目的走向也不受赵成的控制,这个尝试最终以失败告终。
但赵成和github团队并没有放弃,而是着手开发另一个类似node-webkit的项目:Atom Shell,这个项目就是Electron的前身,赵成在这个项目上倾注了大量的心血,这也是这个项目后来广受欢迎的关键因素之一,再后来github把这个项目开源出来,最终更名为Electron。
你可能从没听说过这两个名字,但开源界就是有这么一批“英雄”,他们不为名利而来,甘做软件行业发展的铺路石,值得这个领域的所有从业者尊敬(以上内容与Electron的作者确认过)。
注入脚本
Electron内部拥有一个完整得浏览器核心,你可以用程序操纵这个浏览器核心,让它加载一个第三方网页,比如:淘宝的生意参谋、网易云音乐、gitee等,但单单加载这些网页,并没有什么稀奇的,毕竟在浏览器里也能加载这些网页。最有意思的是,你还可以给这些网页注入脚本,比如像下面这样,注入一个Js文件到目标网页:
代码语言:javascript复制let win = new BrowserWindow({
webPreferences: {
preload: path.join(appPath, 'yourPreload.js'),
nodeIntegration: true
}
});
win.loadURL('https://www.baidu.com/');
如果你只希望注入一两句代码,也可以通过如下形式注入脚本:
代码语言:javascript复制let decryptStr = await this.win.webContents.executeJavaScript(`window.__allen_decrypt('${encryptStr}')`);
这些脚本是在目标网页的作用域下执行的,与目标网页的工程师自己写的代码没什么区别。想象一下,你如果想调用目标网页的某个服务端接口,你是不是应该考虑如何模拟token,如何跨域等等问题,现在你只需要在脚本里直接写调用接口的逻辑就可以了。
另外,如果你不喜欢目标网页的页面样式,你也可以直接注入一段样式脚本,代码如下:
代码语言:javascript复制let key = await win.webContents.insertCSS("html, body { }");
//await win.webContents.removeInsertedCSS(key); //在未来的某个时刻,还可以移除掉这个样式的作用
值得一提的是,你注入的脚本还可以访问Node.js的API。也就是说,你在脚本中获取到了目标网页的资源后,可以直接写到你本地文件里。
突破同源策略的限制
注入了脚本,获取到了受限的资源,你可能希望把这些资源提交到你自己的服务器上,或者你可能希望在注入的脚本里,访问另一个网站的API,以获取更多的资源,这个时候,如果没做特殊配置的话,同源策略就会起作用,限制你这么干,浏览器往往会报如下错误:
代码语言:javascript复制Access to XMLHttpRequest at 'https://www.domain1.com/aggsite/AggStats' from origin 'http://www.domain2.com:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
“同源策略”是浏览器的一个安全功能,“同源”是指如果两个页面的协议(http/https)、端口和主机都相同,则两个页面具有相同的源。同源策略规定不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。只有同一个源的脚本才具备读写cookie、session、ajax等的操作的权限。
在Electron中突破同源策略,就是一两个配置的事情,代码如下:
代码语言:javascript复制let win = new BrowserWindow({
width: 800,height: 600,
webPreferences: {
nodeIntegration: true,
webSecurity: false, //此参数禁用当前窗口的同源策略
}
})
win.loadURL('https://www.baidu.com/');
这样设置之后,浏览器的所有同源策略限制就全部失效了。如果你只是希望在https域下访问http的资源,那么你可以不用关掉整个同源策略,只需要把allowRunningInsecureContent这个配置设置为true即可(它同样也是webPreferences下的一个属性)。如果你设置webSecurity为false了,那么allowRunningInsecureContent会自动设置为true。
读写受限访问的Cookie
由于浏览器每次与服务端的交互,都会携带同域下的所有Cookie,所以网站开发者往往会把标记用户身份的信息(比如用户token)放到Cookie里。
一般情况下,前端开发工程师可以使用document.cookie访问浏览器里存储的同域的Cookie,但也有例外,凡标记了HttpOnly的Cookie,通过这种方式都是访问不到的。网站开发者之所以这么做,主要是为了防止跨站脚本攻击(XSS)和跨站请求攻击(CSRF)。
跨站脚本攻击(XSS,是Cross Site Scripting的缩写),一旦网站允许用户提交内容,并且会在网站的某些页面上显示用户提交的内容,比如留言或者博客,那么不做防范的话,就有可能受到跨站脚本攻击。恶意用户会在提交内容时在内容中夹带一些恶意JavaScript脚本,当其他用户访问页面时,浏览器会运行这些恶意脚本,恶意脚本有可能会窃取用户的Cookie、页面上的用户隐私信息等,并发送到恶意用户的服务器。他们可以通过这些窃取来的信息模拟用户身份完成非法操作。这就是跨站脚本攻击。 跨站请求攻击(CSRF,是Cross Site Request Forgery的缩写),当用户登录了自己信赖的网站后,用户身份信息(token)会被保存保存在用户的浏览器上,后来用户又不小心打开了一个恶意网站,这个恶意网站可能会要求浏览器请求用户信赖的网站(通过iframe等形式),如果用户信赖的网站没有做安全防范的话,可能会被恶意网站获取到用户的敏感信息(token),从而给用户带来伤害。
但这个限制在Electron面前也不值一提,我们可以通过下面这种方式读写受限访问的Cookie:
代码语言:javascript复制//获取Cookie
async function(name) {
let cookies = await remote.session.defaultSession.cookies.get({name});
if(cookies.length>0) return cookies[0].value;
else return '';
}
//设置Cookie
async function(cookie) {
await remote.session.defaultSession.cookies.set(cookie);
}
通过这种方式,无论Cookie有没有设置HttpOnly属性,都可以成功读写。
转发/修改请求
有的时候你不单单是希望给第三方网页附加代码逻辑,而是希望侵入式的修改第三方网站自身的代码逻辑。但往往第三方的JavaScript代码是在一个闭包作用域内执行的,你的代码没办法注入到这个作用域内,去访问作用域内的变量或方法,碰到这样的状况该怎么办呢?
此时,你第一步要做的,就是分析清楚它的脚本是如何执行的,是哪个脚本文件执行的。这个过程是一个非常有趣的过程,不可避免的你要用到谷歌浏览器的开发者调试工具,如果对方的代码是压缩过的,你可能还要给它“美化”一下,再逐步调试。
搞清楚逻辑之后,就把他的脚本文件下载下来,然后在这个文件中加上你的逻辑,你的逻辑可能就是粗暴的把它闭包作用域内的变量暴露到window对象上。这样你注入的脚本,就可以访问这个变量了。
修改完这个脚本文件后,把这个脚本文件host到你自己的一个服务器上,然后通过Electron把网页加载这个脚本文件的请求,转发到你自己的服务器上去,这个转发请求的代码如下:
代码语言:javascript复制win.webContents.session.webRequest.onBeforeRequest({ urls: ["https://*/*"] }, async (details, cb) => {
if (details.url === 'https://g.alicdn.com/dt/op-mc/vendors.js') {
cb({ redirectURL: 'http://domain.com/vendors.js' });
} else {
cb({})
}
});
当这个网页再试图加载这个脚本时,得到的结果将是你修改过的脚本。
有些网站对一些敏感数据保护的很好,客户端请求这些数据时,得到的是服务端加密过的数据,客户端执行解密后,再使用这些数据。对于这类网站,Electron的这个能力无疑是非常有力的。
当然,你也可以考虑使用PWA技术里的service worker来办这个事儿,甚至可以自己在客户端模拟一个响应,不用再经由你的服务器转发了。如果你没有自己的服务器,也可以通过Node.js的能力,自己在软件里起一个localhost的服务。所有这些骚操作,都必须时在Electron内执行的哦。
反“防盗链”
有的时候你可能只是想把目标网站的一些静态资源嵌入到你的应用程序中,比如:图片或者视频。然而如果目标网站已经做了防盗链的工作,你这个功能可能就没那么容易实现了。
防盗链的主要目的有两个:一个是版权问题,别人未经授权就使用你的资源,另一个是流量压力的问题,盗链产生了大量的请求,这些请求对于网站运营者来说没任何价值。
防盗链最常见的做法就是识别HTTP的Refer请求头,这个请求头代表着发起请求时前一个网页的地址,网站运维工程师会根据这个Refer请求头来推测出当前请求是否为一个盗链请求(判断这个Refer请求头的内容是不是自己域名下的一个地址)。
Electron允许开发者监听发起请求的事件,并允许开发者在发起请求前修改请求头,我们可以在这个事件里修改这个Refer请求头,代码如下:
代码语言:javascript复制let session = window.webContents.session;
let requestFilter = { urls: ["http://*/*", "https://*/*"] };
session.webRequest.onBeforeSendHeaders(
requestFilter,
(details, callback) => {
if (details.resourceType == "image" && details.method == "GET") {
delete details.requestHeaders["Referer"]; //这里我直接删掉了这个请求头,也可以修改成其他内容
}
callback({ requestHeaders: details.requestHeaders });
}
);
给应用植入socks5代理
Chrome本身是支持代理的,使用Electron你可以通过编程的方式把你的代理内置到你的应用程序中,这样你的用户就可以自由的访问国外的一些网站了:
常见的代理服务器有http代理、https代理和socks代理,socks代理隐蔽性更强,效率更高,速度更快。咱们这里就聊聊如何在Electron应用内植入socks5代理访问网络服务。
代码语言:javascript复制let result = await win.webContents.session.setProxy({
proxyRules: 'socks5://58.218.200.249:2071'
});
win.loadURL('https://www.ipip.net');
上面代码中,我们使用session实例的setProxy方法来为当前页面设置代理,socks代理,代理设置成功后,我们马上使网页加载了一个IP地址查询的网址,在此页面上我们可以看到访问该页面的实际IP地址,如果这里显示的地址是你的代理服务器的所在地的地址,那么说明代理设置成功。
以上这种方法可以给单个渲染进程设置代理,如果你需要给整个应用程序设置代理,可以使用如下代码完成:
app.commandLine.appendSwitch('proxy-server', 'socks5://58.218.200.249:2071');