223 lines
11 KiB
JavaScript
223 lines
11 KiB
JavaScript
/* ============================================================
|
||
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() {
|
||
/* 把「发起授权的当前 H5 地址」经 navigateTo query 带给授权页(backUrl)——
|
||
与 openid 同一条可靠通道(navigateTo onLoad 必达),不依赖 postMessage
|
||
(微信只在后退/销毁/分享时批量派发,时序不可靠:宫格 location.href 换应用后,
|
||
宿主记的 shareHref 可能还是上一个应用的地址,授权结果被送回错的地方)。
|
||
授权页把 backUrl 写进 __AUTH_RESULT__,宿主 onShow 优先用它做回传基址:
|
||
从哪个 H5 触发的授权,授权后就回到哪个 H5。 */
|
||
var url = authPage || '/pages/annex/auth/index';
|
||
var backUrl = '';
|
||
try { backUrl = location.href; } catch (e) {}
|
||
if (backUrl) {
|
||
url += (url.indexOf('?') !== -1 ? '&' : '?') + 'backUrl=' + encodeURIComponent(backUrl);
|
||
}
|
||
window.wx.miniProgram.navigateTo({
|
||
url: url,
|
||
/* 页面不存在 / 页面栈超限(>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 不回调)都提示「请在小程序中打开商城」。
|
||
默认落点 260918 起为「商城」tab(/pages/index/mall,原装装修首页恢复页),
|
||
与 app.js 的 goMall() 保持一致;此前是分类页 /pages/goods_cate/goods_cate。 */
|
||
goMall: function (url) {
|
||
var self = this;
|
||
function go() {
|
||
window.wx.miniProgram.switchTab({
|
||
url: url || '/pages/index/mall',
|
||
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);
|