UNPKG

t-comm

Version:

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

299 lines (298 loc) 12.4 kB
/** * AccountService — 微信 ⇄ QQ 双登录态切换业务编排(零业务耦合) * * 职责: * - 集中管理 wx.onLaunch / wx.onShow 入口(QQ 插件初始化 / 票据回包处理) * - 提供「切换 QQ 账号 / 切换微信账号」入口 * - 切号成功后清本地登录态 + 通知业务方重新走登录流程 * * 设计原则:所有业务依赖(接口域名、post、storage、toast、玩家服务、是否 QQ * 账号判断等)均由调用方通过 `configure()` 注入,本目录不依赖任何外部业务包。 * * 典型用法: * ```ts * import { AccountService } from 't-comm/es/qq-mp'; * * AccountService.configure({ * qqAppId: 123456, * post, * getQQLoginUserInfoHost: () => isTestEnv() * ? 'https://atest.igame.qq.com' * : 'https://a.igame.qq.com', * storage: { * set: (k, v) => oops.storage.set(k, v), * get: (k) => oops.storage.get(k), * remove: (k) => oops.storage.remove(k), * }, * loginInfoStorageKey: apiConfig.loginInfoStorageKey, * isQQAccount, // () => boolean * toast: msg => ToastTip.show(msg), * clearPlayer: () => PlayerService.ins.clear(), * bootstrapPlayer: opts => PlayerService.ins.bootstrap(opts), * }); * * // 启动期 * AccountService.ins.handleAppOnLaunch(); * * // 设置页 * AccountService.ins.switchToQQ(); * AccountService.ins.switchToWx(); * ``` */ import { checkIsQQEnv } from './qq-mini-plugin'; import { QQPostFn, QQStorageLike, QQTicketInfo } from './types'; /** * AccountService 运行所需的所有外部依赖 * * 全部由业务方在启动期通过 `AccountService.configure(deps)` 一次性注入。 */ export interface AccountServiceDeps { /** QQ 互联 AppId(业务方常量) */ qqAppId: string | number; /** 通用 post 方法(用于调 QueryUserInfo 接口) */ post: QQPostFn; /** * QueryUserInfo 接口域名 * * 支持函数形式以便在测试/正式环境间动态切换。 */ getQQLoginUserInfoHost: string | (() => string); /** * storage 适配器 * * 用于读写 loginInfo / QQ 票据缓存。 * 一般直接桥接到业务方的 oops.storage 即可。 */ storage: QQStorageLike; /** loginInfo 在 storage 中的 key(业务方决定) */ loginInfoStorageKey: string; /** * QQ 票据在 storage 中的 key(可选) * * **不传时默认为 `${loginInfoStorageKey}__qq_ticket`**。 * * 必须与 `loginInfoStorageKey` 区分开:同一个 key 会导致 QQ 票据覆盖业务方身份的 * loginInfo(如 cocos `loginMp` 写入的 `Logininfo` header 结果)。 */ qqTicketStorageKey?: string; /** 当前是否为 QQ 账号(业务方根据自家 loginInfo 结构判断) */ isQQAccount: () => boolean; /** * 是否「在 QQ 环境下强制 QQ 登录态」(可选) * * 默认 `() => true`,即启动期一旦检测到「QQ 环境 + 微信登录态」就清 storage * 让游戏重走 QQ 登录。 * * 若业务期望「QQ App 下也允许手动切到微信账号」,请传入 `() => false`, * 或基于「用户是否手动切到微信账号」返回动态值。 */ shouldForceQQ?: () => boolean; /** * QQ App 环境下【切换 QQ 账号】的后台探身入口(可选) * * 不传则 fallback 到“跳腾讯 QQ 小程序”(launchQQMP)路径。 * * 实现参考 [src/cocos/login/login.ts](../cocos/login/login.ts) 中的 `loginMp`: * 拿到 code 后调业务后台的「_ltype=tiploginqqproc」路径换登录态。 * * @example * code2QQLogin: code => loginMp({ * url: getApiHost() + '/login', * appid: WX_APP_ID, * _ltype: 'tiploginqqproc', * storage, * onLoginInfo: (info) => updateLocalLoginInfo(info), * }), * * 注:loginMp 内部会自己调 wx.login 拿 code;AccountService 这里会优先用 * `qqPluginLogin()` 拿到 QQ code 传入 ,以便业务后台拿到的是 QQ 账号的 code。 */ code2QQLogin?: (code: string) => Promise<unknown> | unknown; /** * QQ App 环境下【切换微信账号】的后台探身入口(可选) * * 不传则 fallback 到“清 storage + bootstrapPlayer”的原路径(适用于微信宿主)。 * * 实现参考 [src/cocos/login/login.ts](../cocos/login/login.ts) 中的 `loginMp`: * 拿到 code 后调业务后台的「_ltype=tiploginwxproc」路径换登录态。 */ code2WxLogin?: (code: string) => Promise<unknown> | unknown; /** * 【微信宿主下】拿到 QQ 票据后的后台探身入口(可选但强烈推荐) * * 背景:微信宿主下 QQ 登录路径是 * 1. `launchQQMP` 跳腾讯 QQ 小程序 * 2. QQ 小程序 navigateBack 后、`wx.onShow` 从 referrerInfo 中提取到 QQ 票据 * 3. 依据票据走业务后台换取登录态 * * 本函数负责第三步。业务侧一般会用 * [src/cocos/login/login.ts](../cocos/login/login.ts) 的 `loginMp({ * _ltype: 'tiploginqqproc', * code: ticket.qqAccessToken, * ... * })` 实现,`loginMp` 会负责读 `Logininfo` 响应头并写入 storage。 * * **未传时的 fallback**:内部调 `queryQQLoginUserInfo`(快路,仅 H5 等响应头 * 能被项目统一拦截的环境可用)。 * * @example * qqTicket2Login: ticket => loginMp({ * url: getApiHost() + '/login', * appid: WX_APP_ID, * _ltype: 'tiploginqqproc', * code: ticket.qqAccessToken, * storage, * storageKey: loginInfoStorageKey, * }), */ qqTicket2Login?: (ticket: QQTicketInfo) => Promise<unknown> | unknown; /** * 显示 toast 提示 * * 由业务方决定使用 ToastTip / wx.showToast 等何种实现。 */ toast: (message: string) => void; /** 清空玩家本地缓存(如 PlayerService.ins.clear) */ clearPlayer: () => void; /** * 强制重新启动玩家服务(如 PlayerService.ins.bootstrap({ force: true })) */ bootstrapPlayer: (options: { force: boolean; }) => Promise<unknown> | void; /** * 登录成功后的业务回调(可选) * * 触发时机:`switchToQQ` / `switchToWx` 的所有成功路径(含「已是当前账号」的幂等分支) * 都会在 `bootstrapPlayer` 拿到玩家数据后调用本函数。 * * 典型用途:业务方在此派发全局事件、关闭登录页、上报埋点等。 * * 语义: * - `platform`:当前登录态平台 * - `reason`:本次触发原因,业务方可用来区分「首次登录 / 切号 / 幂等重登」 * * @example * onLoginSuccess: ({ platform, reason }) => { * oops.message.dispatch(EventName.AuthAccepted, { platform, reason }); * oops.gui.close(UIID.Auth); * } */ onLoginSuccess?: (info: { platform: 'qq' | 'wx'; reason: LoginSuccessReason; }) => void; } /** * 登录成功回调的触发原因 * * - `switch`:从另一平台切换到当前平台(有实际登录动作) * - `already`:调用切号入口时已经是目标平台(幂等分支,仅 bootstrap 拉最新数据) */ export type LoginSuccessReason = 'switch' | 'already'; /** * AccountService 文案配置(可选覆盖默认中文文案) */ export interface AccountServiceMessages { alreadyQQ?: string; alreadyWx?: string; switchQQFail?: string; switchWxSuccess?: string; switchWxFail?: string; switchQQRetry?: string; switchQQSuccess?: string; } export declare class AccountService { private static _ins; private static _deps; private static _messages; /** * 注入运行所需依赖(应用启动期调用一次即可) * * 多次调用会覆盖之前的依赖,便于测试时替换。 */ static configure(deps: AccountServiceDeps, messages?: AccountServiceMessages): void; /** 获取当前注入的依赖(未注入时抛错) */ private static getDeps; /** 单例 */ static get ins(): AccountService; /** QQ 票据存储 key(默认 `${loginInfoStorageKey}__qq_ticket`) */ private static getQQTicketStorageKey; /** 防止 wx.onShow 重复绑定 */ private _onShowBound; /** 防止"切号过程中再次点击"重复触发 */ private _switching; private constructor(); /** 当前是否为 QQ 账号(基于注入的 isQQAccount) */ isQQ(): boolean; /** 「切换 QQ 账号 / 切换微信账号」按钮的动态文案 */ getSwitchButtonText(): string; /** * onLaunch / Game.bootstrap 阶段调用: * * 1) 初始化 qq-wxmini-plugin(QQ 用户访问微信小游戏的插件) * 2) 若当前是 QQ 环境但本地仍是微信登录态,自动清空 storage 让游戏重走 QQ 登录 * 3) 主动绑定 wx.onShow:QQ 登录小程序返回时提取票据并换登录态 */ handleAppOnLaunch(): void; /** * 「切换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` 换登录态。 */ switchToQQ(): Promise<void>; /** * 「切换微信账号」点击(当前账号是 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 走微信登录。 */ switchToWx(): Promise<void>; /** * QQ App 环境下「切换 QQ 账号」的直达路径:插件 login 拿 code → 交业务探后台 */ private _switchToQQViaPlugin; /** * QQ App 环境下「切换微信账号」的直达路径:wx.login 拿 code → 交业务探后台 */ private _switchToWxViaCode; /** * 绑定 wx.onShow(幂等): * - 启动时由 handleAppOnLaunch 调用一次 * - 业务页面再次需要可重复调用 */ private _bindWxOnShow; /** wx.onShow 回调:处理从腾讯 QQ 小程序返回的票据 */ private _onWxShow; /** * 用 QQ 票据换取登录态 + 重启玩家服务 * * 优先级: * 1. 业务侧注入了 `qqTicket2Login`(推荐,如 cocos `loginMp(_ltype=tiploginqqproc, code=qqAccessToken)`)→ 走它。 * 2. 否则 fallback 到 `queryQQLoginUserInfo`(仅适用于能统一拦截 `Logininfo` 响应头的环境)。 */ private _consumeQQTicket; /** * 统一登录成功收尾: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 */ private _finishLoginOk; } export { checkIsQQEnv };