t-comm
Version:
专业、稳定、纯粹的工具库
294 lines (290 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
/**
* 微信小程序/小游戏 ⇄ 腾讯 QQ 小程序 登录互通能力(核心实现)
*
* 文档参考:
* - https://doc.weixin.qq.com/doc/w3_AQQARgb8AOsUIV9oM3SQTG0ERPg9D
* - https://doc.weixin.qq.com/doc/w3_AEIAjwYaACMCNak2HLbrYSemshvHF
* - https://git.woa.com/qzone-web-v2/mini-program
*
* 设计原则:零外部业务耦合
* - 不依赖 @tencent/pmd-network、@tencent/pmd-tools、oops 等业务包
* - 接口域名、post 方法、QQ 互联 AppId 均由调用方注入
* - 仅依赖 wx.* 全局 API(小程序/小游戏内置)
*/
// ============ 默认常量 ============
/** 腾讯 QQ 小程序的 appId(默认值,可被参数覆盖) */
var DEFAULT_QQ_MP_APP_ID = 'wx26da53d900421226';
/** QQ 登录页路径(默认值,可被参数覆盖) */
var DEFAULT_QQ_LOGIN_PATH = 'pagesLogin/pages/login/login';
/** QueryUserInfo 接口路径(默认值,可被参数覆盖) */
var DEFAULT_QUERY_USER_INFO_PATH = 'pmdtrpc.commcgi.user.user/QueryUserInfo';
// ============ 内部工具 ============
/**
* 安全获取 wx 全局对象
*/
function getWX() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return typeof wx !== 'undefined' ? wx : globalThis.wx;
}
/**
* 默认 storage:基于 wx.setStorageSync / wx.getStorageSync
*/
function getDefaultStorage() {
var w = getWX();
return {
set: function set(key, value) {
var _a;
(_a = w === null || w === void 0 ? void 0 : w.setStorageSync) === null || _a === void 0 ? void 0 : _a.call(w, key, value);
},
get: function get(key) {
var _a;
var v = (_a = w === null || w === void 0 ? void 0 : w.getStorageSync) === null || _a === void 0 ? void 0 : _a.call(w, key);
return v === '' || v == null ? undefined : v;
},
remove: function remove(key) {
var _a;
(_a = w === null || w === void 0 ? void 0 : w.removeStorageSync) === null || _a === void 0 ? void 0 : _a.call(w, key);
}
};
}
/**
* 简易 query 拼接(小程序环境不支持 URLSearchParams)
*/
function buildQuery(params) {
return Object.keys(params).filter(function (k) {
return params[k] !== undefined && params[k] !== null && params[k] !== '';
}).map(function (k) {
return "".concat(encodeURIComponent(k), "=").concat(encodeURIComponent("".concat(params[k])));
}).join('&');
}
/**
* 解析 host:支持字符串或函数
*/
function resolveHost(host) {
var h = typeof host === 'function' ? host() : host;
// 去除末尾斜杠
return h.replace(/\/+$/, '');
}
// ============ 核心 API ============
/**
* 从 App.onShow 的 options 中提取 QQ 登录票据
*
* @param options App.onShow 回调参数
* @param qqMpAppId 腾讯 QQ 小程序 appId(默认 wx26da53d900421226)
* @returns QQ 登录票据信息,未登录或失败时返回 undefined
*/
function getQQTicketInfo(options) {
var qqMpAppId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_QQ_MP_APP_ID;
var _a;
var referrerInfo = options === null || options === void 0 ? void 0 : options.referrerInfo;
if (!referrerInfo || referrerInfo.appId !== qqMpAppId) {
return;
}
var extraData = referrerInfo.extraData;
if (!((_a = extraData === null || extraData === void 0 ? void 0 : extraData.tickets) === null || _a === void 0 ? void 0 : _a.length)) {
return;
}
var retcode = extraData.retcode,
tickets = extraData.tickets;
var ticket = tickets[0];
// 票据获取失败
if (retcode !== 0 || !ticket) {
return;
}
var name = ticket.name,
qqTicket = ticket.ticket,
qqAppid = ticket.appid,
qqOpenid = ticket.openid,
qqRefreshToken = ticket.refresh_token,
qqExpiresIn = ticket.expires_in,
qqIsNew = ticket.is_new;
var qqAccessToken = name === 'access_token' ? qqTicket : '';
return {
qqOpenid: qqOpenid,
qqAccessToken: qqAccessToken,
qqRefreshToken: qqRefreshToken,
qqAppid: qqAppid,
qqExpiresIn: qqExpiresIn,
qqIsNew: qqIsNew
};
}
/**
* 处理 QQ 登录返回:提取票据并写入 storage
*
* 通常在 App.onShow 中调用:
* ```ts
* onShow(options => {
* const ticket = handleQQLoginOnShow(options);
* if (ticket) {
* // 用 ticket 调 queryQQLoginUserInfo
* }
* });
* ```
*
* @param options App.onShow 回调参数
* @param config 配置(QQ 小程序 appId / 自定义 storage / storage key)
* @returns 提取到的票据信息,若非 QQ 登录回调或失败则返回 undefined
*/
function handleQQLoginOnShow(options, config) {
var _ref = config || {},
_ref$qqMpAppId = _ref.qqMpAppId,
qqMpAppId = _ref$qqMpAppId === void 0 ? DEFAULT_QQ_MP_APP_ID : _ref$qqMpAppId,
storage = _ref.storage,
storageKey = _ref.storageKey;
var ticketInfo = getQQTicketInfo(options, qqMpAppId);
console.info('[qq-mp] get qq ticket info:', ticketInfo);
if (!ticketInfo) {
return;
}
try {
var store = storage || getDefaultStorage();
store.set(storageKey, ticketInfo);
} catch (e) {
console.error('[qq-mp] save qq ticket failed:', e);
}
return ticketInfo;
}
/**
* 从 storage 读取已保存的 QQ 登录票据
*/
function readQQTicketInfo(config) {
var _ref2 = config || {},
storage = _ref2.storage,
storageKey = _ref2.storageKey;
try {
var store = storage || getDefaultStorage();
return store.get(storageKey);
} catch (e) {
console.error('[qq-mp] read qq ticket failed:', e);
return;
}
}
/**
* 清除已保存的 QQ 登录票据
*/
function clearQQTicketInfo(config) {
var _a;
var _ref3 = config || {},
storage = _ref3.storage,
storageKey = _ref3.storageKey;
try {
var store = storage || getDefaultStorage();
(_a = store.remove) === null || _a === void 0 ? void 0 : _a.call(store, storageKey);
} catch (e) {
console.error('[qq-mp] clear qq ticket failed:', e);
}
}
/**
* 跳转腾讯 QQ 小程序进行 QQ 登录
*
* 调用后 QQ 小程序登录完成,会通过 wx.navigateBackMiniProgram 返回,
* 在本小程序的 App.onShow 中通过 referrerInfo.extraData 拿到票据。
*
* @param options 必填 qqAppId(QQ 互联 AppId)
*/
function launchQQMP(options) {
var qqAppId = options.qqAppId,
_options$qqMpAppId = options.qqMpAppId,
qqMpAppId = _options$qqMpAppId === void 0 ? DEFAULT_QQ_MP_APP_ID : _options$qqMpAppId,
_options$loginPath = options.loginPath,
loginPath = _options$loginPath === void 0 ? DEFAULT_QQ_LOGIN_PATH : _options$loginPath,
_options$forcePwd = options.forcePwd,
forcePwd = _options$forcePwd === void 0 ? false : _options$forcePwd,
_options$retcodes = options.retcodes,
retcodes = _options$retcodes === void 0 ? [] : _options$retcodes,
envVersion = options.envVersion;
return new Promise(function (resolve, reject) {
var w = getWX();
if (!(w === null || w === void 0 ? void 0 : w.navigateToMiniProgram)) {
reject(new Error('[qq-mp] wx.navigateToMiniProgram not available'));
return;
}
var params = {
appId: qqMpAppId,
path: loginPath,
extraData: {
// 是否强制输入密码(关闭快速登录)
force_pwd: forcePwd,
// 透传给 QQ 小程序的状态码列表
retcodes: retcodes,
// 票据列表(目前仅支持 access_token)
tickets: [{
// 票据类型,目前仅支持 access_token,不区分大小写
name: 'access_token',
// 需要登录的小程序的 QQ 互联 appid
appid: "".concat(qqAppId)
}],
// 用于统计耗时
clickTime: Date.now()
},
success: function success(res) {
return resolve(res);
},
fail: function fail(err) {
return reject(err);
}
};
if (envVersion) {
params.envVersion = envVersion;
}
w.navigateToMiniProgram(params);
});
}
/**
* 使用 QQ 票据请求后台 QueryUserInfo 接口换取登录态
*
* 接口域名、post 方法均由调用方注入,组件不耦合任何业务包。
*
* @param options 必填 host / post / ticketInfo
* @returns 后台返回的用户信息;票据无效或失败时返回 undefined
*/
function queryQQLoginUserInfo(options) {
var host = options.host,
post = options.post,
ticketInfo = options.ticketInfo,
_options$apiPath = options.apiPath,
apiPath = _options$apiPath === void 0 ? DEFAULT_QUERY_USER_INFO_PATH : _options$apiPath,
fallbackQQAppId = options.fallbackQQAppId,
extraQuery = options.extraQuery,
extraReqData = options.extraReqData;
if (!(ticketInfo === null || ticketInfo === void 0 ? void 0 : ticketInfo.qqOpenid) || !ticketInfo.qqAccessToken) {
console.warn('[qq-mp] queryQQLoginUserInfo skipped, no ticket');
return Promise.resolve(undefined);
}
if (!post) {
console.error('[qq-mp] queryQQLoginUserInfo missing `post` function');
return Promise.resolve(undefined);
}
var queryParams = Object.assign({
openid: "".concat(ticketInfo.qqOpenid),
appid: "".concat(ticketInfo.qqAppid || fallbackQQAppId || ''),
access_token: "".concat(ticketInfo.qqAccessToken),
acctype: 'qq'
}, extraQuery || {});
var query = buildQuery(queryParams);
var baseHost = resolveHost(host);
var path = apiPath.replace(/^\/+/, '');
var url = "".concat(baseHost, "/").concat(path).concat(query ? "?".concat(query) : '');
console.info('[qq-mp] queryQQLoginUserInfo url:', url);
return post({
url: url,
reqData: extraReqData || {}
}).then(function (res) {
console.info('[qq-mp] queryQQLoginUserInfo res:', res);
return res;
})["catch"](function (err) {
console.error('[qq-mp] queryQQLoginUserInfo failed:', err);
return undefined;
});
}
exports.DEFAULT_QQ_LOGIN_PATH = DEFAULT_QQ_LOGIN_PATH;
exports.DEFAULT_QQ_MP_APP_ID = DEFAULT_QQ_MP_APP_ID;
exports.DEFAULT_QUERY_USER_INFO_PATH = DEFAULT_QUERY_USER_INFO_PATH;
exports.clearQQTicketInfo = clearQQTicketInfo;
exports.getQQTicketInfo = getQQTicketInfo;
exports.handleQQLoginOnShow = handleQQLoginOnShow;
exports.launchQQMP = launchQQMP;
exports.queryQQLoginUserInfo = queryQQLoginUserInfo;
exports.readQQTicketInfo = readQQTicketInfo;