現在,以編程方式在 Electron 中上傳文件,是非常簡單的!

必要的上下文

想盡快熟悉上下文語境的,可以點這裏: https://github.com/electron/electron/issues/749

這段討論,其實本來是討論如何自動設置 input 標籤的值來實現自動選擇文件的.前一段有個 Electon 中自動上傳文件的需求,被 Google 帶到了這個討論地址.雖然,最後當時是採用的不同討論中的本地代理器轉發cookie的策略,但不得不承認,這些討論還是給了自己很大啓發的 – 雖然暫時並沒有什麼用.

It’s near impossible to programmatically upload a file in electron right now.

當時,討論區 @erikmellum 的一句 “現在在Electron 中,以編碼方式上傳文件,幾乎是不可能的”,讓我放棄了對 Electron 本身機制的思考.轉而,基於當時 App 已有的本地代理服務器, 做了另一番嘗試.當然,最後也是成功了.這個機制,等會兒我會簡單描述下.因爲它已經不是重點了! 因爲已經有了更簡化的方式.

這個問題的關鍵是在於如何獲取完整的 cookie,特別是 session 相關那一部分的 cookie.今天突然又看到 electron 文檔的 session 部分,看到它有一個 cookies 屬性.心想,這個 cookies, 既然是屬於 electron 自有的 api,那豈不是也可以獲取完整的 cookie ?試了下,還真是! 卒~

技術關鍵點分析

Electron,結合了 Node 和 Chromium.在相當程度上,可以認爲它同時擁有了 Node 和 Chromium 的能力;另一方面,其實也可以認爲,它擁有了 Node 和 Chromium 各自的限制.在 Electron 編碼時,如何理解和運用 Node 和 Chromium 各自的限制和能力,就變得很有趣.如果能進一步地熟練打通 Node 和 Chromium ,真的可以做某些以前很難去想象的事.

具體到以編碼方式上傳文件這個問題上.這個問題的完整描述應該是類似於這樣: 網站有自己的登錄認證機制,在不需要在對網站登錄機制做任何修改的前提下,如何自動上傳用戶相關的文件,比如用戶頭像?

我們就以自動上傳用戶頭像爲例.我們可以假定已經通過某種方式,得到了用戶頭像的本地路徑.–這個大前提,在基於 Electron 的App中,非常容易滿足!

對於 Chromium 側的童鞋來說,拿到文件的本地路徑後,是沒有比較簡便的辦法實現文件上傳的. https://github.com/electron/electron/issues/749#issuecomment-145764807 討論中,提到的兩個地址,再結合 https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects 或許應該也是可以的.我沒驗證!

對於 Node 層的同學來說,有了文件路徑,可以很容易地通過 https://www.npmjs.com/package/request 庫來實現文件的上傳,如果他能拿到當前回話的完整 Cookie 的話.當然,這個限制,也是有足夠多的方式來彌補的,比如讓用戶在桌面 App 上,再單獨登錄一次.不管怎樣,解決問題就好.

但是,Electron 提供了一種全新的可能.它讓你可以在 Node 側,直接拿到 Chromium 側的完整 Cookie.然後你就可以使用 Node 的方式,以最精簡的代碼,最符合直覺的方式來處理文件上傳.

好吧,借題插一句:我曾經處理過一個 XML 文件解析的需求.當時搜了各種 Node 庫,都沒太好使的,後來我是直接在 render process 中,直接用 html 的dom 接口去讀取和解析的 xml 文件! – 當時,被自己的機智驚呆了! 講真, 使用 Electron 來開發桌面 App,你真正需要考慮的不是如何實現某個需求,而是如何以一種更優雅的方式來實現需求! 沒有做不到,只有不敢想啊!!!

一個簡單的實例: 實現開源中國用戶頭像自動更換

效果圖

爲了完整演示這一技術可能涉及的特定問題及其解決方案,我們就從真實環境中來構造一個需求: 實現開源中國用戶頭像自動更換. 當然,是不需要 OSC 的研發改任何代碼的前提下!!!

安裝 electron

安裝,建議使用穩定版本 1.3.x 系列的,可能需要 科學上網,才能安裝.基礎的快速入門教程,參考: https://electron.atom.io/docs/tutorial/quick-start/

npm i electron@1.3.15 -g

jquery 無效問題.

我們把入門示例中的url換爲 osc 官方的域名:

win.loadURL("https://www.oschina.net/")

cd 到項目目錄,執行:

electron .

此時 electron 就運行起來了,不過當你切換到登錄頁後, devtool 窗口,應該會報錯:

Uncaught ReferenceError: $ is not defined

錯誤的原因,可以參考: https://github.com/electron/electron/issues/254 不過解決方式,都不適用於這裏的場景.因爲我不能直接修改 OSC 網站源碼.

不過 Electron 創建窗口時,提供了一個 preload 參數,允許注入一個 js 文件到網頁上下中:

  win = new BrowserWindow({width: 1300, height: 720, webPreferences:{
    preload:path.resolve("./osc-preload.js")
  }})

然後,我們可以重寫在注入的js中,重寫 window 的 $ 和 jquery 屬性的 getter 方法:

Object.defineProperties(window,{
    "$":{
            get: function () {
                return require("jquery")
            },
            configurable : true
        },
      "jQuery":{
              get: function () {
                  return require("jquery");
              },
              configurable : true
          }
    });

此處,之所以是重寫getter,而不是直接賦值,是因爲 jquery 依賴於特定的 dom 結構,但是預注入的js文件在執行時,是沒有任何 dom 結構的.注入的js文件,執行時機非常早,甚至早過 dom 或任意其他css/js 文件 的加載或渲染.

有關重寫 getter 和 setter 方法的技術細節,大家可以參考這裏: https://segmentfault.com/a/1190000003882976

尋找jquery版本

當然,此處需要我們先在本地項目中,先安裝 jquery 依賴,從 osc 源碼中分析出,它用的 2.2.4 版本,我們最好也安裝對應版本:

npm i jquery@2.2.4 --save

找到頭像上傳接口

發現頭像上傳接口

這個很容易,只要通過 ctrl/cmd + alt + i 快捷鍵打開devtool,然後自己替換下頭像,找到那個 ajax 請求就可以了.

我們注意到 formData 主要有 img 和 user_code 兩部分構成.

構造 img 字段

其實就是一個圖片的 base 編碼, Node 搞這個,輕輕鬆啊.
先安裝一個工具庫: base64-img

npm install base64-img --save

然後:

/* 我們有足夠豐富的方式來獲取或計算圖片的路徑,此處默認採用的方式就是:
當前目錄下的 test.jpeg 圖片.
另外,此處文件注意使用 jpeg 後綴.這要是 OSC 本身的限制.*/
const imgPath = path.resolve(__dirname,"./test.jpeg")

/* 此處,將文件轉換爲 base64,只是因爲 osc 的頭像變更接口,設計如此!! */
const imgData = base64Img.base64Sync(imgPath)

分析並獲取 user_code

分析並獲取 user_code

你要相信,任何在 Electron 打開的網站,即時你不是網站的擁有者,也可以獲取比網站的前端研發人員更多的信息. Electron 的機制使然.

只要在 devtool 的源碼區域,簡單搜索下,就很容易發現 user_code 的來源.壓縮後的源碼,如果看着不輸入,可以點擊源碼視圖區左下角的格式化按鈕 {} 格式化一下.

所以獲取 user_code 的代碼,就是:

    g = $("val[data-name=user_code]").data("value"),

Electron 有想成的 API 可以用.Cookie 操作的文檔,參見:https://electron.atom.io/docs/api/cookies/

const { session } = require('electron').remote

session.defaultSession.cookies.get({
        url: window.location.href
    }, (error, cookies) => {
      console.log(cookies)
    })

使用 request 庫發送頭像更新請求

request 的詳細文檔,參考: https://www.npmjs.com/package/request.需要先安裝依賴:

npm i request --save
const request = require("request")

request({
    url: `https://my.oschina.net/action/user/save_portrait_new`,
    method: "POST",
    formData: {
        img: imgData,
        user_code: userCode
    },
    headers: {
        "cookie": cookieStr
    }
}, function(err, response, body) {
  console.log(err, response, body)

  const {error} = JSON.parse(body)
  if ( ! error) {
    window.location.reload()
  }
})

實例完整源碼

感興趣的童鞋,記得 Star 關注下 !!!

https://github.com/ios122/demo-electron-share-cookie

這個思考,主要是基於當期 App 的現狀 – 已經有了一個用於加速靜態資源訪問速度的用作緩存功能的本地代理服務器,還有就是當時也對 Electron 的 session 和 cookie 的接口,不太熟悉, 而採用的臨時措施.但畢竟可用,順便說下.其中的本地代理服務器部分,還是有一定參考價值的.

關於本地代理服務器,大家可以看下 微信小程序開發工具,會看到它的網絡請求的 remote address 都是本地地址,很明顯它加了本地代理服務器.

本地代理服務器本身,可以使用 https://www.npmjs.com/package/http-proxy 這個庫.

Electron 想要支持設置網絡代理,主要是用到 https://electron.atom.io/docs/api/session/#sessetproxyconfig-callback 接口.唯一主要注意的是,這個接口必須在 main process 調用,纔會生效.

使用本地代理服務器獲取完整 cookie 的思路是: 約定某個url路徑,比如 /-fetch-all-cookies 爲獲取 cookie 的路徑 –> 前端發送 ajax 請求到 /-fetch-all-cookies –> 本地代理服務器,攔截到請求,如果發現路徑是 /-fetch-all-cookies,就把當次請求的 header 中的cookie 部分,作爲返回值返回 –> 前端獲取到完整 cookie –> 前端調用 request 庫,上傳文件.

雖然不是最優最簡方案,但或許可以算得上是 腦洞開的最大的方案!!! O(∩_∩)O哈哈哈~

參考鏈接:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章