Hash, PushState 和微信 JSSDK 受權

最近將 riot.js 升級到了 3.0,並用上了新版本的 riot-route,原先用了一年多的 2.2.4 版本內置的 riot.route 只支持 hash 形式的 SPA 單頁面應用,riot-route 則支持 pushState。api

Hash 方式有個缺點,就是服務器不知道地址欄中 # 以後的內容,放在微信裏,就致使了未受權用戶受權後想返回原界面須要藉助 JS 來實現,致使歷史記錄多了一條,用戶按返回鍵沒法退出(返回上一頁面後又被 JS 「前進」了)。瀏覽器

PushState 方式就沒有這個問題,能夠直接用 HTTP 302 重定向過來,不影響歷史記錄和返回按鈕功能。服務器

因而在新項目中開始採用 pushState 方式。微信

不過等到應用到了微信環境中,又冒出來一個問題,經過 pushState 改變地址在 iOS 中會致使 JSSDK 的受權失敗。app

在 config 中開啓 debug,可見 invalid signature 。緣由是 location.href 中的地址變化了,可是微信客戶端認爲地址仍是打開頁面時的地址,微信「複製連接」獲得的地址能夠做爲旁證。函數

解決方法

先按標準方法用當前地址計算 signature,若是失敗,再用打開瀏覽器時的地址計算 signature。ui

因爲以前已經把加載微信 JSSDK 和相關 Api 的功能獨立成函數,所以此次解決起來也比較簡單。url

修改前的代碼
function runWx(api, fn, loadError) {
    if (typeof api === 'string') {
        api = [api];
    }

    // 用 require.js 加載 JSSDK 文件,若是你已經用 <script> 方式加載,能夠省掉這一步
    require(['jweixin'], function (wx) {
        var url = location.href.split('#')[0];
        
        // 將 url 發往服務器計算相應的 signature
        $.get('/signature', { url: url }, function (ans) {
            // wx.config 執行成功時調用
            wx.ready(function () {
                wx.checkJsApi({
                    jsApiList: _.clone(api), // 坑,微信會改變此參數的內容
                    success: function success(res) {
                        if (!res || !res.checkResult || _.any(api, function (v) {
                            return !res.checkResult[v];
                        })) {
                            alert('您的微信版本太低,沒法使用此功能,請升級微信');
                            return;
                        }
                        fn(wx);
                    }
                });
            });

            // wx.config 執行失敗時調用
            wx.error(function () {
                alert('受權失敗,您可能沒法使用部分功能');
            });

            // 校驗用的參數來自服務器
            var config = {
                // debug: true,
                appId: ans.data.appId,
                timestamp: ans.data.timestamp,
                nonceStr: ans.data.nonceStr,
                signature: ans.data.signature,
                jsApiList: _.union(['checkJsApi'], api)
            };

            // 進行校驗
            wx.config(config);
        });
    }, loadError);
}

先簡單展現一下這個 runWx 函數的用法:debug

// 當須要用到特定的微信接口時,運行 runWx
runWx(['uploadImage', 'chooseImage'], function (wx) {
    // 能夠在這裏使用 wx.uploadImage 和 wx.chooseImage 功能
    wx.uploadImage(...);
    wx.chooseImage(...);
}, function () {
    // 加載失敗時要作的事
})

runWx 作了這麼幾件事:code

  • 用 require.js 加載 weixin sdk 的 js 文件
  • 獲取當前頁面地址 location.href.split('#')[0]
  • 將 url 做爲參數發送到服務器計算出 signature
  • 調用 wx.config
  • 成功時調用 wx.ready 並最終調用回調函數 fn
  • 失敗時調用 wx.error 提示錯誤
修改後的代碼
// 解決部分機型 pushState 不能正確改變地址致使受權失敗

// 在第一次打開頁面時加載此文件,記錄當時的地址做爲原始地址
var originUrl = location.href.split('#')[0];

// 增長 tryOrigin 參數
function runWx(api, fn, loadError, tryOrigin) {
    if (typeof api === 'string') {
        api = [api];
    }
    require(['jweixin'], function (wx) {
        // tryOrigin 爲真時使用原始地址
        var url = tryOrigin ? originUrl : location.href.split('#')[0];
        
        $.get('/signature', { url: url }, function (ans) {
            wx.ready(function () {
                wx.checkJsApi({
                    jsApiList: _.clone(api), // 坑,微信會改變此參數的內容
                    success: function success(res) {
                        if (!res || !res.checkResult || _.any(api, function (v) {
                            return !res.checkResult[v];
                        })) {
                            alert('您的微信版本太低,沒法使用此功能,請升級微信');
                            return;
                        }
                        fn(wx);
                    }
                });
            });

            wx.error(function () {
                // 已經試了原始地址
                if (originUrl === url) {
                    alert('受權失敗,您可能沒法使用部分功能');
                    return;
                }
                // 沒有使用原始地址且 signature 不匹配,嘗試用原始地址計算 signature
                runWx(api, fn, loadError, true);
            });

            var config = {
                debug: true,
                appId: ans.data.appId,
                timestamp: ans.data.timestamp,
                nonceStr: ans.data.nonceStr,
                signature: ans.data.signature,
                jsApiList: _.union(['checkJsApi'], api)
            };
            wx.config(config);
        });
    }, loadError);
}

和原來相比,主要變化有:

  • 記錄了打開瀏覽器時的原始地址
  • 先嚐試用標準的 location.href.split('#')[0] 計算 signature
  • 失敗時用 originUrl 再試一次

這個改動的主要優勢是原來用到 runWx 的地方,代碼徹底不須要進行變更,由 runWx 本身去嘗試解決問題。

相關文章
相關標籤/搜索