Files
mama-box/js/wx-auth.js

209 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
wx-auth.js · H5 应用通用授权 + 跳商城 共享助手(各 H5 应用引用即可)
------------------------------------------------------------
依赖:页面引入 <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
用法:在 H5 页面里
<script src="wx-auth.js"></script>
然后:
wxAuth.init(); // 检测环境 + 读取已授权资料(hash) + 回填
wxAuth.isAuthorized(); // 是否已授权(手机号/昵称/头像任一非空)
wxAuth.requestProfile(); // 点「授权登录」→ postMessage 通知宿主页
wxAuth.goMall(); // 点「进商城」→ 跳小程序商城 tabBar 页
wxAuth.watchLocation(); // 换页/路由切换自动上报当前位置 → 宿主刷新分享卡片(推荐)
wxAuth.reportLocation({title}); // 手动上报一次当前页(标题/图可选)
分享卡片同步协议(location/share/setTitle/copyLink/requestProfile)见
box_app/docs/h5-auth-standard.md 第四节
回传协议:宿主页授权成功后更新 web-view src 为
<H5地址>?t=<ts>#openid=..&phone=..&nickname=..&avatar=..
(微信 webview 丢 query、不丢 hash,故授权结果放 hash)
============================================================ */
(function (g) {
'use strict';
function readHash(name) {
try {
var h = location.hash.slice(1) + '&' + location.search.slice(1);
var m = h.match(new RegExp('(?:^|[&#])' + name + '=([^&#]*)(?:$|[&#])'));
return m ? decodeURIComponent(m[1]) : '';
} catch (e) { return ''; }
}
var wxAuth = {
MODE: 'browser', // 'webview' | 'wechat-browser' | 'browser'
inWebview: false,
onReady: null,
/* ① 环境检测 + 读回传资料(授权后宿主更新 src,本页重载读到 hash) */
init: function (cb) {
this.onReady = cb || null;
var self = this;
var isWechat = /MicroMessenger/i.test(navigator.userAgent);
function setMode(m) {
self.MODE = m; self.inWebview = (m === 'webview');
if (self.onReady) self.onReady(self);
}
if (window.wx && window.wx.miniProgram) {
var responded = false;
window.wx.miniProgram.getEnv(function (r) {
responded = true;
setMode(r && r.miniprogram ? 'webview' : (isWechat ? 'wechat-browser' : 'browser'));
});
setTimeout(function () { if (!responded) setMode(isWechat ? 'wechat-browser' : 'browser'); }, 800);
} else {
setMode(isWechat ? 'wechat-browser' : 'browser');
}
},
/* ② 读取授权结果(hash 里的 phone/nickname/avatar) */
readAuth: function () {
return { phone: readHash('phone'), nickname: readHash('nickname'), avatar: readHash('avatar') };
},
/* ③ 是否已授权 */
isAuthorized: function () {
var a = this.readAuth();
return !!(a.nickname || a.avatar || a.phone);
},
/* ④ 请求授权:navigateTo → 小程序端独立授权页(默认 pages/annex/auth/index)。
改动说明:微信 web-view 的 postMessage 非实时送达,点击时端上收不到;
改用 wx.miniProgram.navigateTo 实时跳转到授权页,避开该限制。 */
/* ④ 请求授权:环境判定(与 goMall/mama-box 同一套):
① UA 不含 MicroMessenger = 普通浏览器 → 直接提示(秒出,不等超时);
② 微信内 → getEnv 确认是否 webview(纯浏览器加载了 jweixin 时 getEnv
不回调,800ms 超时兜底)→ 是小程序才 navigateTo 授权页。 */
requestProfile: function (authPage) {
var self = this;
function nav() {
window.wx.miniProgram.navigateTo({
url: authPage || '/pages/annex/auth/index',
/* 页面不存在 / 页面栈超限(>10层) 时 navigateTo 是静默失败 —— 必须自己兜住并提示,
否则用户只看到「正在打开授权页…」后再无反应,无从排查。 */
fail: function (err) {
var m = (err && err.errMsg) || '未知原因';
self.toast('打开授权页失败:' + m);
try { console.warn('[wxAuth] navigateTo fail:', m); } catch (e) {}
}
});
self.toast('正在打开授权页…');
}
if (!/MicroMessenger/i.test(navigator.userAgent)) {
this.toast('请在小程序中完成授权登录');
return;
}
if (window.wx && window.wx.miniProgram) {
var responded = false;
window.wx.miniProgram.getEnv(function (r) {
responded = true;
if (r && r.miniprogram) nav();
else self.toast('请在小程序中完成授权登录');
});
setTimeout(function () {
if (!responded) self.toast('请在小程序中完成授权登录');
}, 800);
} else {
this.toast('请在小程序中完成授权登录');
}
},
/* ⑤ 跳小程序商城(tabBar 页用 switchTab;非 tabBar 用 navigateTo) */
/* ⑤ 跳商城:环境判定与 requestProfile 同一套 —— 仅小程序 webview 内 switchTab,
UA 非微信/微信内非 webview(或 getEnv 不回调)都提示「请在小程序中打开商城」。 */
goMall: function (url) {
var self = this;
function go() {
window.wx.miniProgram.switchTab({
url: url || '/pages/goods_cate/goods_cate',
fail: function (err) {
var m = (err && err.errMsg) || '未知原因';
self.toast('打开商城失败:' + m);
try { console.warn('[wxAuth] switchTab fail:', m); } catch (e) {}
}
});
}
if (!/MicroMessenger/i.test(navigator.userAgent)) {
this.toast('请在小程序中打开商城');
return;
}
if (window.wx && window.wx.miniProgram) {
var responded = false;
window.wx.miniProgram.getEnv(function (r) {
responded = true;
if (r && r.miniprogram) go();
else self.toast('请在小程序中打开商城');
});
setTimeout(function () {
if (!responded) self.toast('请在小程序中打开商城');
}, 800);
} else {
this.toast('请在小程序中打开商城');
}
},
/* ⑥ 展示授权结果到指定元素(可选便捷方法) */
fillAuth: function (nameEl, avatarEl) {
var a = this.readAuth();
if (nameEl && a.nickname) nameEl.textContent = a.nickname;
if (avatarEl && a.avatar) avatarEl.src = a.avatar;
},
/* ⑦ 上报"当前在哪一页"(分享状态同步,不弹 toast、可频繁调用)。
为什么需要:web-view 里的 H5 不能自己拉起分享面板,转发卡片由小程序宿主页产出;
宿主只有 onShareAppMessage(options).webViewUrl 能拿到当前地址,而
onShareTimeline(朋友圈)**没有回调参数**;首页壳又不会随 H5 换页重新 onLoad。
所以应用在「页面/路由切换」时上报一次 location,宿主即可刷新分享图/标题/深链。
协议:{action:'location', href, title?, summary?, imageUrl?} → 详见 docs/h5-auth-standard.md 第四节
opts.imageUrl 给了就以 H5 为准(仅当前应用);不给则用 box_app 后台配置的分享图。 */
reportLocation: function (opts) {
opts = opts || {};
if (!(window.wx && window.wx.miniProgram && window.wx.miniProgram.postMessage)) return false;
try {
window.wx.miniProgram.postMessage({
data: {
action: 'location',
href: opts.href || location.href, // 可传 href:用于"即将跳转到的地址"上报
title: opts.title || '',
summary: opts.summary || '',
imageUrl: opts.imageUrl || ''
}
});
return true;
} catch (e) {
try { console.warn('[wxAuth] reportLocation fail:', e); } catch (e2) {}
return false;
}
},
/* ⑧ 自动监听页面切换并上报(首屏一次 + hashchange/popstate + pushState/replaceState)。
用法(推荐在 init 回调里调用):
wxAuth.watchLocation(function () {
return { title: document.title }; // 可选:本页分享标题/图
});
同一 href 只会发一次(内部去重),SPA 与多页应用都适用。 */
watchLocation: function (getShare) {
var self = this;
var last = '';
function pull() {
if (location.href === last) return;
last = location.href;
var s = (typeof getShare === 'function') ? (getShare() || {}) : {};
self.reportLocation(s);
}
pull(); // 首屏
if (g.addEventListener) {
g.addEventListener('hashchange', pull);
g.addEventListener('popstate', pull);
}
/* history.pushState/replaceState 不触发上面两个事件,需要包一层 */
['pushState', 'replaceState'].forEach(function (fn) {
var orig = g.history && g.history[fn];
if (typeof orig !== 'function') return;
g.history[fn] = function () {
var r = orig.apply(this, arguments);
setTimeout(pull, 0);
return r;
};
});
return self;
},
toast: function (m) {
var t = document.getElementById('toast');
if (!t) return;
t.textContent = m; t.classList.add('show');
clearTimeout(t._tt); t._tt = setTimeout(function () { t.classList.remove('show'); }, 1800);
}
};
g.wxAuth = wxAuth;
})(window);