t-comm
Version:
专业、稳定、纯粹的工具库
299 lines (298 loc) • 12.4 kB
TypeScript
/**
* 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 };