解決火狐新窗口打開網頁被攔截問題

閱讀更多系列文章請訪問個人GitHub 博客,本文示例代碼請訪問這裏

Demo 運行方式

請在這裏下載示例代碼,並在終端中運行以下命令啓動 Demo:html

  1. 安裝依賴
npm install
複製代碼
  1. 啓動 Demo
npm start
複製代碼

示例代碼的服務端,由Koa實現。前端

因爲谷歌瀏覽器無此問題,請在**火狐瀏覽器**中,打開http://localhost:8080/進行測試。git

問題描述

出現問題的場景

  1. 用戶點擊支付按鈕。github

  2. 前端發起一個 AJAX 請求到服務端。npm

  3. 服務端返回一個由支付寶生成的表單,其中帶有支付頁地址,以及相關參數。json

  4. 前端使用表單的參數,在新窗口打開支付寶的支付頁面。瀏覽器

在火狐瀏覽器中,該操做會被瀏覽器攔截,以下圖所示:bash

被火狐瀏覽器攔截

結論是,只要在 AJAX 後,用 JavaScript 觸發的跳轉,都會被火狐攔截。app

可以正常打開的場景

固然,火狐並不是對全部場景都進行了攔截,例如這些操做:koa

  1. 進行 AJAX 請求後,在當前窗口打開連接。

  2. 由用戶直接點擊後,在新窗口打開連接。

前端示例代碼

// 經過直接點擊,能夠在新窗口打開。
// 經過a標籤打開和表單提交一樣能夠。
document
  .querySelector('#open')
  .addEventListener('click', function () {
    window.open(action);
  });

// 經過計時器延遲以後,依然能夠在新窗口打開。
document
  .querySelector('#open')
  .addEventListener('click', function () {
    setTimeout(() => {
      window.open(action);
    }, 100);
  });
複製代碼

失敗的解決方案

列舉幾個失敗的方案,能夠在http://localhost:8080/中點擊相應按鈕查看效果。

前端示例代碼

  1. window.open
// 獲取支付寶支付的表單
async function getFormStr() {
  const response = await fetch('/getFormStr');
  const result = await response.json();

  document.querySelector('#formWrapper').innerHTML = result.formStr;

  const action = document
    .querySelector('#formWrapper form')
    .getAttribute('action');

  return action;
}

// 方法1:window.open
document
  .querySelector('#method1')
  .addEventListener('click', async function () {
    const action = await getFormStr();

    window.open(action);
  });
複製代碼
  1. form.submit
// 方法2:form.submit
document
  .querySelector('#method2')
  .addEventListener('click', async function () {
    const action = await getFormStr();
    let formEle = document.createElement('form');

    formEle.style.display = 'none';
    formEle.method = 'post';
    formEle.target = '_blank';
    formEle.action = action;

    document.body.appendChild(formEle);

    formEle.submit();
  });
複製代碼
  1. a 標籤打開
// 方法3:a標籤打開
document
  .querySelector('#method3')
  .addEventListener('click', async function () {
    const action = await getFormStr();
    let anchorEle = document.createElement('a');

    anchorEle.href = action;
    anchorEle.target = '_blank';

    document.body.appendChild(anchorEle);

    anchorEle.click();
  });
複製代碼

服務端示例代碼

// 獲取支付寶支付的表單
router.get('/getFormStr', async (ctx, next) => {
  const response = await fetch(
    'http://rap2.taobao.org:38080/app/mock/250475/getFormStr',
  );
  const result = await response.json();

  ctx.body = result;

  await next();
});
複製代碼

成功的解決方案

該問題的解決思路以下:

  1. 在前端用 form 表單,打開新窗口提交參數。

  2. 由服務端進行處理以後,直接重定向到支付寶的支付頁。

前端示例代碼:

  1. 在頁面中建立一個表單,存儲相應數據。
<button id="method4">方法4:服務端重定向</button>
<form id="redirectForm" action="/redirectForm" target="_blank"></form>
複製代碼
  1. 點擊按鈕以後,打開新窗口,並提交表單。
// 方法4:服務端重定向
document
  .querySelector('#method4')
  .addEventListener('click', async function () {
    document.querySelector('#redirectForm').submit();
  });
複製代碼

服務端示例代碼以下:

// 獲取支付寶支付的表單,以後重定向到支付寶支付頁
router.get('/redirectForm', async (ctx, next) => {
  const response = await fetch(
    'http://rap2.taobao.org:38080/app/mock/250475/getFormStr',
  );
  const result = await response.json();

  // 進行Entity轉換
  const decodedFormStr = he.decode(result.formStr, {
    isAttributeValue: true,
  });
  const decodedFormStrArr = decodedFormStr.split('\n');
  // 截取表單中的有用參數,並拼接成跳轉支付寶的URL
  const aliPayUrl = `${decodedFormStrArr[0].match(/action="([\s|\S]*)">/)[1]}&${
    decodedFormStrArr[1].match(/name="([\s|\S]*)" value="/)[1] }=${decodedFormStrArr[1].match(/value="([\s|\S]*)">/)[1]}`; ctx.response.redirect(aliPayUrl); await next(); }); 複製代碼

Entity 的轉換

因爲支付寶返回的表單中,有 &quot; 字符,即爲 Entity(實體),若是直接表單寫入頁面,瀏覽器可以正確將其識別成 "

若是你想要查看完整的 Entity 列表,能夠點擊這裏

但在服務端只能將其當作字符串處理,所以須要用he庫進行轉換,避免參數拼接出錯,以下:

// 進行Entity轉換
const decodedFormStr = he.decode(result.formStr, {
  isAttributeValue: true,
});
複製代碼

小結

  1. 如今前端的能力愈來愈強,但始終仍是有其侷限性,解決問題的時候不該當把思路侷限在前端領域,要充分利用服務端的資源。

  2. Entity 雖然不多使用,但遇到的時候能夠經過he庫進行轉換。

相關文章
相關標籤/搜索