UNPKG

t-comm

Version:

专业、稳定、纯粹的工具库

384 lines (381 loc) 16.1 kB
import _classCallCheck from '@babel/runtime/helpers/classCallCheck'; import _createClass from '@babel/runtime/helpers/createClass'; import { initQQMiniPlugin, clearWxLoginStorageIfQQEnv, checkIsQQEnv, qqPluginLogin, wxLogin } from './qq-mini-plugin.mjs'; export { checkIsQQEnv } from './qq-mini-plugin.mjs'; import { launchQQMP, clearQQTicketInfo, handleQQLoginOnShow, queryQQLoginUserInfo } from './qq-mp.mjs'; var DEFAULT_MESSAGES = { alreadyQQ: '当前已是 QQ 账号', alreadyWx: '当前已是微信账号', switchQQFail: '切换QQ账号失败,请稍后重试', switchWxSuccess: '切换微信账号成功', switchWxFail: '切换微信账号失败,请稍后重试', switchQQRetry: '切换QQ账号失败,请重试', switchQQSuccess: '切换QQ账号成功' }; // ============ 实现 ============ var AccountService = /*#__PURE__*/function () { function AccountService() { _classCallCheck(this, AccountService); /** 防止 wx.onShow 重复绑定 */ this._onShowBound = false; /** 防止"切号过程中再次点击"重复触发 */ this._switching = false; } // ============ 账号类型查询 ============ /** 当前是否为 QQ 账号(基于注入的 isQQAccount) */ return _createClass(AccountService, [{ key: "isQQ", value: function isQQ() { return AccountService.getDeps().isQQAccount(); } /** 「切换 QQ 账号 / 切换微信账号」按钮的动态文案 */ }, { key: "getSwitchButtonText", value: function getSwitchButtonText() { return this.isQQ() ? '切换微信账号' : '切换QQ账号'; } // ============ App 生命周期入口 ============ /** * onLaunch / Game.bootstrap 阶段调用: * * 1) 初始化 qq-wxmini-plugin(QQ 用户访问微信小游戏的插件) * 2) 若当前是 QQ 环境但本地仍是微信登录态,自动清空 storage 让游戏重走 QQ 登录 * 3) 主动绑定 wx.onShow:QQ 登录小程序返回时提取票据并换登录态 */ }, { key: "handleAppOnLaunch", value: function handleAppOnLaunch() { var deps = AccountService.getDeps(); try { initQQMiniPlugin(); } catch (e) { console.warn('[AccountService] initQQMiniPlugin 失败', e); } try { clearWxLoginStorageIfQQEnv({ // 当前 loginInfo 不是 QQ 账号 → 视为微信登录态 isWxLoggedIn: function isWxLoggedIn() { var loginInfo = deps.storage.get(deps.loginInfoStorageKey); if (!loginInfo) return false; return loginInfo.accType !== 'qq' && loginInfo.openid_type !== 'qq'; }, // 用注入的 storage 兜底清,避免误删 wx 全局其它业务数据 // 同时清掉可能遗留的 QQ 票据,避免下轮启动又被误识别为 QQ 登录。 clearStorage: function clearStorage() { var _a, _b, _c, _d; (_b = (_a = deps.storage).remove) === null || _b === void 0 ? void 0 : _b.call(_a, deps.loginInfoStorageKey); (_d = (_c = deps.storage).remove) === null || _d === void 0 ? void 0 : _d.call(_c, AccountService.getQQTicketStorageKey(deps)); }, // 业务可选控制是否「QQ 环境下强制 QQ 登录」 shouldForceQQ: deps.shouldForceQQ }); } catch (e) { console.warn('[AccountService] clearWxLoginStorageIfQQEnv 失败', e); } this._bindWxOnShow(); } // ============ 切号入口(供页面层调用) ============ /** * 「切换QQ账号」点击入口 * * 根据运行环境自动分流: * - QQ App 环境(`checkIsQQEnv() === true`)且业务传入了 `code2QQLogin`: * 调用 `qqPluginLogin()` 直接拿 QQ code → 交 `code2QQLogin` 探后台换登录态。 * 业务后台路径参考 [src/cocos/login/login.ts](../cocos/login/login.ts) 的 `loginMp`(`_ltype=tiploginqqproc`)。 * - 微信宿主环境(或未传 `code2QQLogin`): * `launchQQMP` → 腾讯 QQ 小程序登录 → `wx.onShow` 提取票据 → `QueryUserInfo` 换登录态。 */ }, { key: "switchToQQ", value: function switchToQQ() { var _this = this; var deps = AccountService.getDeps(); var messages = AccountService._messages; if (this._switching) return Promise.resolve(); if (this.isQQ()) { // 幂等分支:已是 QQ 账号仍需要跑一次「bootstrapPlayer + onLoginSuccess」, // 让业务侧能在登录页也命中登录成功回调(AuthPage 场景: // 用户已是 QQ 账号 → 只需拉玩家数据 + close 登录页,不用重新走 QQ 授权) this._switching = true; deps.toast(messages.alreadyQQ); return this._finishLoginOk('qq', 'already'); } this._switching = true; // QQ App 环境 + 业务传了后台探身入口 → 走「plugin.login() + code2QQLogin」直达路径 if (deps.code2QQLogin && checkIsQQEnv()) { return this._switchToQQViaPlugin(); } // 微信宿主环境 → 跳腾讯 QQ 小程序,后续由 wx.onShow → _consumeQQTicket 完成 return launchQQMP({ qqAppId: deps.qqAppId }).then(function () { // 真正的登录完成在 wx.onShow → _consumeQQTicket 中 })["catch"](function (e) { console.error('[AccountService] launchQQMP 失败', e); deps.toast(messages.switchQQFail); _this._switching = false; }); } /** * 「切换微信账号」点击(当前账号是 QQ 时) * * 根据运行环境自动分流: * - QQ App 环境(`checkIsQQEnv() === true`)且业务传入了 `code2WxLogin`: * 调用 `wxLogin()` 拿微信 code → 交 `code2WxLogin` 探后台换登录态。 * 业务后台路径参考 [src/cocos/login/login.ts](../cocos/login/login.ts) 的 `loginMp`(`_ltype=tiploginwxproc`)。 * - 微信宿主环境(或未传 `code2WxLogin`): * 微信小游戏本身就是微信宿主,"切回微信"等价于:清掉 QQ 登录态(loginInfo + QQ 票据缓存) * → 让 SDK 重新用 wx.login 走微信登录。 */ }, { key: "switchToWx", value: function switchToWx() { var _this2 = this; var deps = AccountService.getDeps(); var messages = AccountService._messages; if (this._switching) return Promise.resolve(); if (!this.isQQ()) { // 幂等分支:与 switchToQQ 对齐(详见 switchToQQ 注释) this._switching = true; deps.toast(messages.alreadyWx); return this._finishLoginOk('wx', 'already'); } this._switching = true; // QQ App 环境 + 业务传了后台探身入口 → 走「wx.login + code2WxLogin」直达路径 if (deps.code2WxLogin && checkIsQQEnv()) { return this._switchToWxViaCode(); } // 微信宿主环境 → 清掉 QQ 登录残留 + 统一收尾 return Promise.resolve().then(function () { var _a, _b; clearQQTicketInfo({ storage: deps.storage, storageKey: AccountService.getQQTicketStorageKey(deps) }); (_b = (_a = deps.storage).remove) === null || _b === void 0 ? void 0 : _b.call(_a, deps.loginInfoStorageKey); }).then(function () { return _this2._finishLoginOk('wx', 'switch'); })["catch"](function (e) { console.error('[AccountService] switchToWx 失败', e); deps.toast(messages.switchWxFail); _this2._switching = false; }); } // ============ 内部直达路径实现 ============ /** * QQ App 环境下「切换 QQ 账号」的直达路径:插件 login 拿 code → 交业务探后台 */ }, { key: "_switchToQQViaPlugin", value: function _switchToQQViaPlugin() { var _this3 = this; var deps = AccountService.getDeps(); var messages = AccountService._messages; return qqPluginLogin().then(function (_ref) { var code = _ref.code; if (!deps.code2QQLogin) { // 理论上 switchToQQ 里已拦住;这里双保险 throw new Error('code2QQLogin not configured'); } return Promise.resolve(deps.code2QQLogin(code)); }).then(function () { return _this3._finishLoginOk('qq', 'switch'); })["catch"](function (e) { console.error('[AccountService] _switchToQQViaPlugin 失败', e); deps.toast(messages.switchQQFail); _this3._switching = false; }); } /** * QQ App 环境下「切换微信账号」的直达路径:wx.login 拿 code → 交业务探后台 */ }, { key: "_switchToWxViaCode", value: function _switchToWxViaCode() { var _this4 = this; var deps = AccountService.getDeps(); var messages = AccountService._messages; return wxLogin().then(function (_ref2) { var code = _ref2.code; var _a, _b; if (!deps.code2WxLogin) { throw new Error('code2WxLogin not configured'); } // 先清掉本地残留的 QQ 票据,避免下轮启动又被误识别为 QQ 登录 try { clearQQTicketInfo({ storage: deps.storage, storageKey: AccountService.getQQTicketStorageKey(deps) }); (_b = (_a = deps.storage).remove) === null || _b === void 0 ? void 0 : _b.call(_a, deps.loginInfoStorageKey); } catch (e) { console.warn('[AccountService] clear local before wx login failed', e); } return Promise.resolve(deps.code2WxLogin(code)); }).then(function () { return _this4._finishLoginOk('wx', 'switch'); })["catch"](function (e) { console.error('[AccountService] _switchToWxViaCode 失败', e); deps.toast(messages.switchWxFail); _this4._switching = false; }); } /** * 绑定 wx.onShow(幂等): * - 启动时由 handleAppOnLaunch 调用一次 * - 业务页面再次需要可重复调用 */ }, { key: "_bindWxOnShow", value: function _bindWxOnShow() { var _this5 = this; if (this._onShowBound) return; var w = typeof wx !== 'undefined' ? wx : globalThis.wx; if (!(w === null || w === void 0 ? void 0 : w.onShow)) return; this._onShowBound = true; w.onShow(function (options) { return _this5._onWxShow(options); }); } /** wx.onShow 回调:处理从腾讯 QQ 小程序返回的票据 */ }, { key: "_onWxShow", value: function _onWxShow(options) { var deps = AccountService.getDeps(); var ticket; try { ticket = handleQQLoginOnShow(options, { storage: deps.storage, // 重要:使用独立的票据 key,避免覆盖业务 loginInfo(如 cocos `loginMp` 写入的) storageKey: AccountService.getQQTicketStorageKey(deps) }); } catch (e) { console.warn('[AccountService] handleQQLoginOnShow 异常', e); } if (!ticket) return; this._consumeQQTicket(ticket); } /** * 用 QQ 票据换取登录态 + 重启玩家服务 * * 优先级: * 1. 业务侧注入了 `qqTicket2Login`(推荐,如 cocos `loginMp(_ltype=tiploginqqproc, code=qqAccessToken)`)→ 走它。 * 2. 否则 fallback 到 `queryQQLoginUserInfo`(仅适用于能统一拦截 `Logininfo` 响应头的环境)。 */ }, { key: "_consumeQQTicket", value: function _consumeQQTicket(ticket) { var _this6 = this; var deps = AccountService.getDeps(); var messages = AccountService._messages; // 优先走业务注入的「票据 → 后台探身」,该函数内部应完成 logininfo 写入。 if (typeof deps.qqTicket2Login === 'function') { return Promise.resolve().then(function () { return deps.qqTicket2Login(ticket); }).then(function () { return _this6._finishLoginOk('qq', 'switch'); })["catch"](function (e) { console.error('[AccountService] qqTicket2Login 失败', e); deps.toast(messages.switchQQFail); _this6._switching = false; }); } return queryQQLoginUserInfo({ host: deps.getQQLoginUserInfoHost, post: deps.post, ticketInfo: ticket, fallbackQQAppId: deps.qqAppId }).then(function (rsp) { if (!(rsp === null || rsp === void 0 ? void 0 : rsp.user_info)) { deps.toast(messages.switchQQRetry); _this6._switching = false; return; } // QueryUserInfo 在响应头/cookie 里写入 QQ loginInfo(由网络层统一处理)。 return _this6._finishLoginOk('qq', 'switch'); })["catch"](function (e) { console.error('[AccountService] _consumeQQTicket 失败', e); deps.toast(messages.switchQQFail); _this6._switching = false; }); } /** * 统一登录成功收尾:clearPlayer → bootstrapPlayer → toast → onLoginSuccess → 释放 _switching * * 说明: * - `switching` 语义与 UI 层「登录中」loading 是同一件事,因此在 bootstrap 完成前 * 保持为 true;bootstrap 结果 resolve 或 reject 都释放 * - toast 文案根据 platform 选:QQ = switchQQSuccess;WX = switchWxSuccess * - onLoginSuccess 在 toast 之后调,业务侧派事件/关登录页由回调决定 * - bootstrapPlayer 失败仍会触发 onLoginSuccess?NO:bootstrap 抛错代表玩家数据 * 没拉到,登录不算完整成功,跳过 toast 与 onLoginSuccess,让业务侧看到 catch */ }, { key: "_finishLoginOk", value: function _finishLoginOk(platform, reason) { var _this7 = this; var deps = AccountService.getDeps(); var messages = AccountService._messages; deps.clearPlayer(); return Promise.resolve(deps.bootstrapPlayer({ force: true })).then(function () { var _a; deps.toast(platform === 'qq' ? messages.switchQQSuccess : messages.switchWxSuccess); try { (_a = deps.onLoginSuccess) === null || _a === void 0 ? void 0 : _a.call(deps, { platform: platform, reason: reason }); } catch (e) { console.error('[AccountService] onLoginSuccess 回调异常', e); } })["catch"](function (e) { console.error('[AccountService] _finishLoginOk bootstrap 失败', e); deps.toast(platform === 'qq' ? messages.switchQQFail : messages.switchWxFail); throw e; })["finally"](function () { _this7._switching = false; }); } }], [{ key: "configure", value: /** * 注入运行所需依赖(应用启动期调用一次即可) * * 多次调用会覆盖之前的依赖,便于测试时替换。 */ function configure(deps, messages) { AccountService._deps = deps; AccountService._messages = Object.assign(Object.assign({}, DEFAULT_MESSAGES), messages || {}); } /** 获取当前注入的依赖(未注入时抛错) */ }, { key: "getDeps", value: function getDeps() { if (!AccountService._deps) { throw new Error('[AccountService] 未初始化,请先调用 AccountService.configure(deps)'); } return AccountService._deps; } /** 单例 */ }, { key: "ins", get: function get() { if (!AccountService._ins) AccountService._ins = new AccountService(); return AccountService._ins; } // ============ 内部工具 ============ /** QQ 票据存储 key(默认 `${loginInfoStorageKey}__qq_ticket`) */ }, { key: "getQQTicketStorageKey", value: function getQQTicketStorageKey(deps) { return deps.qqTicketStorageKey || "".concat(deps.loginInfoStorageKey, "__qq_ticket"); } }]); }(); AccountService._ins = null; AccountService._deps = null; AccountService._messages = Object.assign({}, DEFAULT_MESSAGES); export { AccountService };