作為一個前端開發人員,你可能已經聽說過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檔案到目標網頁:
let win = new BrowserWindow({ webPreferences: { preload: path.join(appPath, 'yourPreload.js'), nodeIntegration: true } }); win.loadURL('https://www.baidu.com/');
如果你只希望注入一兩句程式碼,也可以通過如下形式注入指令碼:
let decryptStr = await this.win.webContents.executeJavaScript(`window.__allen_decrypt('${encryptStr}')`);
這些指令碼是在目標網頁的作用域下執行的,與目標網頁的工程師自己寫的程式碼沒什麼區別。想象一下,你如果想呼叫目標網頁的某個服務端介面,你是不是應該考慮如何模擬token,如何跨域等等問題,現在你只需要在指令碼里直接寫呼叫介面的邏輯就可以了。
另外,如果你不喜歡目標網頁的頁面樣式,你也可以直接注入一段樣式指令碼,程式碼如下:
let key = await win.webContents.insertCSS("html, body { }"); //await win.webContents.removeInsertedCSS(key); //在未來的某個時刻,還可以移除掉這個樣式的作用
值得一提的是,你注入的指令碼還可以訪問Node.js的API。也就是說,你在指令碼中獲取到了目標網頁的資源後,可以直接寫到你本地檔案裡。
突破同源策略的限制
注入了指令碼,獲取到了受限的資源,你可能希望把這些資源提交到你自己的伺服器上,或者你可能希望在注入的指令碼里,訪問另一個網站的API,以獲取更多的資源,這個時候,如果沒做特殊配置的話,同源策略就會起作用,限制你這麼幹,瀏覽器往往會報如下錯誤:
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中突破同源策略,就是一兩個配置的事情,程式碼如下:
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:
//獲取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把網頁載入這個指令碼檔案的請求,轉發到你自己的伺服器上去,這個轉發請求的程式碼如下:
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請求頭,程式碼如下:
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代理訪問網路服務。
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');
最後
以上內容來自於我的新書《Electron實戰:入門、進階與效能優化》,
書裡還有更多有趣的內容,
大家感興趣可以加QQ群949674481交流。
噹噹:http://product.dangdang.com/28547952.html;
京東:https://item.jd.com/12867054.html