最近在實現一個關聯谷歌帳戶的需求。看到網上的大部分涉及前端方面的實現都有這麼個OAuth標準下的流程:javascript
通常在受權頁被受權後子窗口(受權窗口)都會被關閉。瀏覽器的每一個窗口是禁止關閉當前窗口的,只能關閉由當前窗口打開的其餘窗口。有圖有真相:html
因此想要關閉子窗口就須要父窗口來操做。而什麼時候讓父窗口關閉子窗口就須要子窗口在完成用戶受權後通知父窗口來關閉本身。這就涉及到父子間窗口的通訊。前端
父子間窗口通訊分兩種狀況java
瀏覽器的同源策略還沒了解到就請自行Google了。
按照OAuth
流程是不會出現父子窗口同源的現象。可是這裏也作一下總結。後端
子窗口是由父窗口建立的。父窗口能夠在打開子窗口後獲取到子窗口的引用,經過這個引用能夠觸發子窗口的方法以此向子窗口傳遞消息瀏覽器
// parent code let child_window_handle = null; $('#open-child-win-btn').on('click', () => { child_window_handle = window.open('target_url.html', '_blank', 'width=700, height=500, left=200'); })
這個時候有一個子窗口的句柄了(handler)。
而子窗口的頁面下有以下方法網絡
// child code function ProcessParentMsg(msg) { // do something with the msg }
父窗口只須要在調用子窗口的對應方法就能夠和子窗口完成通訊異步
// parent code child_window_handle.ProcessParentMsg('msg_form_parent_window');
子窗口能夠經過window對象的opener屬性訪問到父窗口。而且調用父窗口的方法來完成向上通訊。post
// child code window.opener.ProcessChildMsg();
// parent code function ProcessChildMsg(msg) { // do something with msg }
父子窗口同源的狀況下,父窗口是能夠很大程度的控制子窗口的。除了能夠觸發子窗口的方法,也能夠監聽子窗口的事件,onbeforeunload
、onresize
, focus
等等, 可是父子窗口不一樣源的狀況下。父窗口沒法執行子窗口下的方法,也沒法監聽窗口下的事件。以前設想的關閉子窗口的實現方式是在父窗口得到子窗口的句柄而後監聽子窗口的onload
,onload以後就調用父窗口的用於關閉子窗口的方法。顯然這隻能在同源的狀況下發生了。url
這種狀況下父子窗口要通訊就須要藉助HTML5的message passing
功能了。
直接看示例😄,
在父窗口中向子窗口派發消息
// parent window let child_window_handle = window.open('child_target.html', '_blank', 'width=700, height=500'); child_window_handle.postMessage('Msg to the child window', '*');
在子窗口下監聽消息
// child window window.addEventListener('message', (e) => { ProcessParentMsg(e.data); }); function ProcessParentMsg(msg) { // do something with the msg }
// child window window.opener.postMessage("Message to parent", "*");
// parent window window.addEventListener('message', function(e) { ProcessChildMsg(e.data); }, false); function processChildMsg() { // do something with the message }
當我在實現點擊按鈕打開受權窗口的時候一直出現窗口被攔截的提示,沒法直接打開受權彈窗口。這是由於點擊window.open
這個操做是在異步操做的回調裏面執行的。默認這種狀況下瀏覽器都會攔截這個新窗口,除非用戶設定對這個域名容許任何彈窗。
stackoverflow
上能夠看到這個解釋
The general rule is that popup blockers will engage if window.open or similar is invoked from javascript that is not invoked by direct user action. That is, you can call window.open in response to a button click without getting hit by the popup blocker, but if you put the same code in a timer event it will be blocked. Depth of call chain is also a factor - some older browsers only look at the immediate caller, newer browsers can backtrack a little to see if the caller's caller was a mouse click etc. Keep it as shallow as you can to avoid the popup blockers.
起先當我點擊按鈕的時候我先去經過網絡請求接口獲取受權頁的鏈接。在異步回調裏獲取到了受權頁連接。此時再去用window.open
去打開這個連接。這個不是 direct user action
。即便能夠相信也是一個比較差的用戶體驗,由於形成了延遲。因此修改後的方案就是用戶點擊了關聯按鈕立刻打開一個blank
窗口。同時異步去獲取受權頁連接。獲取後reload打開的受權窗口的地址爲獲取到的鏈接。這就不會致使 popup blocked
的現象發生了。
描述到這裏可能若是沒有受權頁開發經驗的人可能仍是沒法理解是怎麼關閉子頁面的。當用戶打開受權頁後,用戶點擊受權按鈕
此時頁面會跳轉到一個用戶指定的uri。若是未指定的話,會直接顯示authorize code
在窗口中,這一般不是咱們想要的。咱們須要用這個受權碼去換取token
,token是真正能夠登陸用戶帳戶的臨時憑證。因此一般是用戶指定一個uri,這個uri能夠是一個後端接口,受權窗口被用戶受權後會以querystring
的形式帶上code的參數跳轉到咱們提供的uri。這個時候後端接口能夠獲取到受權碼去執行換token的操做。以後接口返回一個text/html
的response,response 返回的內容大體以下:
<html> <head> <meta charset="UTF-8"/> <title>test</title> </head> <body> 帳號關聯成功 </body> <script> window.opener.postMessage('close_child_window', '*'); </script> </html>