v0.8.1 界面六条:删离线入口 / 标语统一 / 最近纪念日 / 图标真上色 / 真头像 / 日程四修
用户 2026-10-18 六条(全部已在真机逐条验证):
① 登录页「先不登录 / 离线使用」彻底删除
用户:「把那些不登录、看离线数据之类的提示全部去掉。以后就不再拥有这样的
功能了,把这些功能也全部删除。」
⇒ 两个按钮 + onSkip + allowSkip 参数 + Index 里的 skip 分支一起删净。
这是产品决定(不是隐藏):该路径本身就是安全缺口,v58 上了整库加密后更要堵死。
② 标语统一为「一家人用岁月写成的一本书」
用户:「这也是我们"家书"这个名字的由来。所以你不要再乱换这个标语了,
只要是想出现标语或者副标题的地方,就用这样一句话。」
· 端上 FB_DEFAULT_SLOGAN 是唯一定义处(登录页副标题 + 抽屉回落都用它);
· 抽屉左上角原来是家庭名,现在显示**标语**;
· 三层优先级(个人 slogan → 家庭 motto → 应用默认口号)**在服务端算**
(Family::slogan,与网页端首页 heroSlogan 同一套),/login 与 /me 都返回。
用户还说:「如果这个用户自己有标语……这个地方就是用户自己自定义的标语。」
真机实测:登录者杨永全自己写了「修身,齐家」⇒ 抽屉就显示他那句。
③ 首页新增「最近纪念日」(放在近期任务之后、往年的今天之前)
用户:「未来最近的三个纪念日,包括每个人的生日,以及手动填写的各个纪念日。
以倒计时形式展示:距离哪个纪念日还有多少天,距离谁谁谁的生日还有多少天。」
· /home.recent_days = Anniversary::upcoming(fam,pid,370) 取前 3 ——
取满一年:窗口太小会让"刚过完生日"的家庭一个都取不到,模块直接空着;
· 数据源 all() = 派生生日 + 自建纪念日,正好覆盖用户说的两样;
· 倒计时:0 天显示「就是今天」(写「还有 0 天」读起来像已经过了)。
④ 悬浮栏图标也上色(用户:「除了下面的汉字,上面图标的颜色也变成跟汉字一样的颜色」)
🔴🔴 真根因两层,都是"无报错失效":
· 第一层:Image.fillColor() **只对 SVG 生效**,而本工程 30+ 图标**全是 PNG**
⇒ 全工程 24 处图标染色一直是空操作(文字走 fontColor 所以只有文字变了)。
· 第二层(真机才现形):改用 colorFilter 后,矩阵写成了"系数乘 RGB"
(R' = R_原 × 系数) —— 对白色图标成立,但本工程图标是**纯黑**,
黑 × 任何系数 = 仍是黑 ⇒ 真机上图标像素依旧是 (0,0,0)。
⇒ 最终形态:`fbTint` 用 **alpha 当载体**(R' = (r/255) × A_原):
系数是归一化约定(官方灰度示例系数和为 1,量纲确定);
形状完全由原图 alpha 决定 ⇒ 黑/白图标都成立,描边抗锯齿自然过渡。
真机像素实测:日程项图标与文字同为 (176,120,64)≈#B0763A;
未选中项图标 (44,39,35) 深灰(不再是纯黑)。
⚠️ 全工程 24 处 fillColor 一并改掉;FbIconButton.tint 类型改成 string,
让"误传 Resource"变成编译错误而不是又一个静默失效。
⑤ 顶栏:真实头像 + 拉开按钮间距
用户:「右上角的头像应该是真实的……现在提醒的按钮距离头像的按钮太近了。」
· /home 给 me_avatar、/login 给 avatar_url(avatar_url() 按线上存储驱动拼好);
· 三颗按钮(同步 / 铃铛 / 头像)统一用 FbMetric.gapTopActions = 8。
⑥ 日程四处
· 「日期下面有几个点」⇒ 月视图与周视图的圆点全部删除,
连 FbDot / dots() / count / dotColor 一起清(不留死代码);
· 「农历全部都是初一」⇒ 真根因是端上 lunarOf() **硬编码 return '初一'**,
而服务端 lunarRange() 本来就每天给真实农历(title)⇒ 改取 title;
· 「列表这个视图,今天的数据缺一条」⇒ 服务端 /calendar 的 from/to **默认只给本月**,
而列表视图要显示 -7 ~ +60 天 ⇒ 端上显式传范围
(loadModule 新增 pathOverride 参数);
· 「点击了某一天之后并不明显,应该把这一天的颜色变深,而今天用一个较浅的颜色」
⇒ 照 SyncCalendar:选中 = 深色实底白字、今天 = 浅色底。
真机实测:17 号(选中)bg=#FFB0763A,18 号(今天)bg=#FFF5F0EA。
另:FbKeyStore 补上一个真机抓到的 HUKS 坑 —— isKeyItemExist 在密钥不存在时
**会抛 12000011**(不是返回 false),首次运行必被误判成"本机不支持 HUKS"
⇒ 永久明文降级。改成把"不存在"当正常分支,且降级态每次启动重试一次
(把同一把 DEK 重新用 KEK 包起来,不换密钥以免重下全部附件)。
验证:hvigor BUILD SUCCESSFUL;_fb_arkts_guard.js 328/0;
_fb_v59ui_falsify.py 19 项注入 23/0;_fb_home_api.py 60/0;
六条已在真机逐条取像素/布局树确认。
This commit is contained in:
@@ -50,6 +50,21 @@ const DEK_LEN: number = 32;
|
||||
const IV_LEN: number = 12;
|
||||
const TAG_LEN: number = 16;
|
||||
|
||||
/**
|
||||
* HUKS「条目不存在」错误码。
|
||||
*
|
||||
* 🔴🔴 2026-10-18 真机实测(harmonyos 真机,非模拟器):
|
||||
* `huks.isKeyItemExist(alias, ...)` 在密钥**不存在**时**会抛异常**,
|
||||
* 而不是文档说的"返回 false" —— 抛出的 code 正是 12000011
|
||||
* (HUKS_ERR_CODE_ITEM_NOT_EXIST;HUKS 服务侧日志为
|
||||
* `HksKeyExist: leave KeyExist, result = -13`,-13 = HKS_ERROR_NOT_EXIST)。
|
||||
*
|
||||
* 首次运行一定走到这条(KEK 还没生成)⇒ 如果不接住这个异常,
|
||||
* 就会被当成"本机不支持 HUKS",直接**永久降级成明文密钥**
|
||||
* (实测症状:设置页显示"已降级(明文保存)",而 HUKS 其实完全可用)。
|
||||
*/
|
||||
const HUKS_ERR_ITEM_NOT_EXIST: number = 12000011;
|
||||
|
||||
class FbKeyStoreImpl {
|
||||
private store: preferences.Preferences | null = null;
|
||||
private dekBytes: Uint8Array | null = null;
|
||||
@@ -110,13 +125,24 @@ class FbKeyStoreImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ① 先用降级路径里那份明文 DEK(上次已经降级过,别再试一遍 HUKS 白等)
|
||||
// ① 上次降级过(明文 DEK 在):先用它把数据救活,但**不要就此认命** ——
|
||||
// HUKS 可能是被临时性原因挡住的(系统刚升级、密钥库服务还没起来…),
|
||||
// 下次启动也许就好了。所以这里试着把同一把 DEK **重新用 KEK 包起来**;
|
||||
// 只有真包不上才继续留在降级态。
|
||||
// 🔴 2026-10-18 为什么这样改:原来只要见过一次 KEY_PLAIN 就永远走降级
|
||||
// ("上次已降级,别再试一遍 HUKS 白等")⇒ 一次误判 ⇒ **终身明文**。
|
||||
// 真机实测正是如此:`isKeyItemExist` 抛的一个"不存在"被误当成"不支持",
|
||||
// 之后每次启动都直接沿用明文,HUKS 明明可用也升不回来。
|
||||
const plainV: preferences.ValueType = await this.store.get(KEY_PLAIN, '');
|
||||
if (typeof plainV === 'string' && plainV !== '') {
|
||||
this.dekBytes = this.b64Decode(plainV);
|
||||
const plainDek: Uint8Array | null = this.b64Decode(plainV);
|
||||
this.dekBytes = plainDek;
|
||||
this.weak = true;
|
||||
this.ready = true;
|
||||
hilog.info(DOMAIN, TAG, '沿用上一次的降级密钥(本机不支持 HUKS)');
|
||||
hilog.info(DOMAIN, TAG, '读到了上次的降级密钥,尝试重新用 HUKS 保护');
|
||||
if (plainDek !== null) {
|
||||
await this.tryUpgradeFromPlain(plainDek);
|
||||
}
|
||||
return this.dekBytes !== null;
|
||||
}
|
||||
|
||||
@@ -126,6 +152,31 @@ class FbKeyStoreImpl {
|
||||
return this.dekBytes !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把"已降级"的那把明文 DEK **重新包进 HUKS**(成功则升回正常态)。
|
||||
*
|
||||
* ⚠️ 只重新包裹、**不换密钥**:附件密文是用这把 DEK 加的,
|
||||
* 换一把会让已落盘的附件全部解不开(白下几百个文件)。
|
||||
* 等价的错误做法是"直接调 loadOrCreate 生成新 DEK" —— 那等于每次
|
||||
* 从降级态恢复都要重下全部附件。
|
||||
*/
|
||||
private async tryUpgradeFromPlain(dek: Uint8Array): Promise<void> {
|
||||
if (this.store === null) {
|
||||
return;
|
||||
}
|
||||
const wrapped: string | null = await this.wrap(dek);
|
||||
if (wrapped === null) {
|
||||
hilog.info(DOMAIN, TAG, 'HUKS 仍不可用,继续使用明文密钥');
|
||||
return;
|
||||
}
|
||||
this.weak = false;
|
||||
this.lastError = '';
|
||||
await this.store.put(KEY_WRAP, wrapped);
|
||||
await this.store.delete(KEY_PLAIN);
|
||||
await this.store.flush();
|
||||
hilog.info(DOMAIN, TAG, '已把降级的密钥重新交给 HUKS 保护(不再明文保存)');
|
||||
}
|
||||
|
||||
/** 读包裹体 → 解包;没有包裹体就新生成一把 */
|
||||
private async loadOrCreate(ctx: common.Context): Promise<void> {
|
||||
if (this.store === null) {
|
||||
@@ -173,10 +224,34 @@ class FbKeyStoreImpl {
|
||||
hilog.info(DOMAIN, TAG, '新数据密钥已生成并用 HUKS 包裹');
|
||||
}
|
||||
|
||||
/**
|
||||
* KEK 是否已存在。
|
||||
*
|
||||
* ⚠️ **不能用** `huks.isKeyItemExist()` 的返回值直接判 —— 它在"不存在"时
|
||||
* 会抛 `12000011`(见 `HUKS_ERR_ITEM_NOT_EXIST` 的说明)。
|
||||
* ⇒ 这里把"不存在"(异常码 / 返回 false)统一成 false,
|
||||
* 真正的不支持(其它错误码)才往上抛,由调用方决定是否降级。
|
||||
*/
|
||||
private async kekExists(): Promise<boolean> {
|
||||
try {
|
||||
return await huks.isKeyItemExist(KEK_ALIAS, { properties: [] });
|
||||
} catch (err) {
|
||||
const e: BusinessError = err as BusinessError;
|
||||
if (e.code === HUKS_ERR_ITEM_NOT_EXIST) {
|
||||
return false; // 正常情况:还没生成过 KEK
|
||||
}
|
||||
// ⚠️ ArkTS 的 `throw` **不接受任意类型**(arkts-limited-throw)——
|
||||
// catch 到的 `err` 是 unknown,`throw err` 编译不过,必须包成 Error。
|
||||
throw new Error('判断 KEK 是否存在失败:code='
|
||||
+ (e.code !== undefined ? e.code : -1)
|
||||
+ ' msg=' + (e.message !== undefined ? e.message : ''));
|
||||
}
|
||||
}
|
||||
|
||||
/** 用 HUKS 的 KEK 加密 DEK,返回 base64(iv|ct|tag);失败返回 null(→ 降级) */
|
||||
private async wrap(dek: Uint8Array): Promise<string | null> {
|
||||
try {
|
||||
const exists: boolean = await huks.isKeyItemExist(KEK_ALIAS, { properties: [] });
|
||||
const exists: boolean = await this.kekExists();
|
||||
if (!exists) {
|
||||
await huks.generateKeyItem(KEK_ALIAS, { properties: this.kekGenProps() });
|
||||
hilog.info(DOMAIN, TAG, 'HUKS 里已生成 KEK(不可导出)');
|
||||
|
||||
@@ -38,6 +38,16 @@ export interface FbLoginResult {
|
||||
role: string;
|
||||
/** 家庭名('' = 服务端没给,不影响登录) */
|
||||
familyName: string;
|
||||
/**
|
||||
* 🔴 v59:该显示的那句**标语**(个人 slogan → 家庭 motto → 应用默认口号)。
|
||||
*
|
||||
* 由服务端 `Family::slogan()` 算好给出来(与网页端首页同一套优先级)——
|
||||
* 端上**不要自己拼**,否则网页端改了个人 slogan、手机上还是老的。
|
||||
* '' = 老服务端没给 ⇒ 端上回落 `FB_DEFAULT_SLOGAN`。
|
||||
*/
|
||||
slogan: string;
|
||||
/** 🔴 v59:我的真实头像 URL('' = 没传过头像,回落色块字) */
|
||||
avatarUrl: string;
|
||||
/** 服务端时间(ISO8601)—— 用来记"刚校验过",'' = 老服务端没给 */
|
||||
serverTime: string;
|
||||
/** 失败原因(ok=false 时有值,可直接展示给用户) */
|
||||
@@ -60,11 +70,15 @@ interface FbApiLoginPerson {
|
||||
id?: number;
|
||||
name?: string;
|
||||
role?: string;
|
||||
/** v59:真实头像 URL(服务端 `avatar_url()` 拼好的) */
|
||||
avatar_url?: string;
|
||||
}
|
||||
|
||||
interface FbApiLoginFamily {
|
||||
id?: number;
|
||||
name?: string;
|
||||
/** v59:该显示的那句标语(服务端已按三层优先级算好) */
|
||||
slogan?: string;
|
||||
}
|
||||
|
||||
interface FbApiEnvelope {
|
||||
@@ -172,6 +186,8 @@ class FbLoginClientImpl {
|
||||
personName: p !== undefined && p.name !== undefined ? p.name : '',
|
||||
role: p !== undefined && p.role !== undefined ? p.role : '',
|
||||
familyName: f !== undefined && f.name !== undefined ? f.name : '',
|
||||
slogan: f !== undefined && f.slogan !== undefined ? f.slogan : '',
|
||||
avatarUrl: p !== undefined && p.avatar_url !== undefined ? p.avatar_url : '',
|
||||
serverTime: d !== undefined && d.server_time !== undefined ? d.server_time : '',
|
||||
error: '',
|
||||
};
|
||||
@@ -183,7 +199,8 @@ class FbLoginClientImpl {
|
||||
function fail(msg: string): FbLoginResult {
|
||||
const r: FbLoginResult = {
|
||||
ok: false, token: '', expiresAt: '', personId: 0, personName: '',
|
||||
role: '', familyName: '', serverTime: '', error: msg
|
||||
role: '', familyName: '', slogan: '', avatarUrl: '',
|
||||
serverTime: '', error: msg
|
||||
};
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -63,12 +63,31 @@ export const FB_MODULE_PATH: Record<string, string> = {
|
||||
* @param parse JSON → 端上类型的解析函数
|
||||
* @param sink 数据到位回调(会被调 1~2 次:缓存一次、网络一次)
|
||||
* @param onNote 可选:提示文案(同步失败/未登录时给页面显示一行小字)
|
||||
* @param pathOverride 可选:**改掉这个模块的请求路径**(带查询串)。
|
||||
*
|
||||
* 🔴🔴 v59(2026-10-18,用户报「列表这个视图,今天的数据缺一条」的真根因):
|
||||
* 服务端 `/api/v1/calendar` 的 `from`/`to` **默认只给"本月"**
|
||||
* (`Api::str('from', date('Y-m-01'))` / `date('Y-m-t')`)。
|
||||
* 而日程页的**列表视图**要渲染 `-7 ~ +60` 天 —— 跨到下个月去,
|
||||
* 下月那一段**服务端根本没返回** ⇒ 落在下月的日程(包括"今天"往后那几天)
|
||||
* 在列表里凭空少掉。
|
||||
* ⚠️ 月视图看起来正常,是因为它本来就只看本月 ⇒ 这个 bug 只在列表暴露。
|
||||
* ⇒ 日程页显式传 `from`/`to`(覆盖它要显示的那一整段),不要依赖默认值。
|
||||
*
|
||||
* ⚠️ 只改**请求路径**、不动缓存键(缓存仍按模块名存):
|
||||
* 否则同一个模块会出现两个缓存键、切视图时互相覆盖。
|
||||
* ⚠️ 这也意味着"列表拉的比月视图多"时,缓存里会留下更宽的一段 ——
|
||||
* 这是**好事**(月视图也能直接命中缓存),但**不要**指望缓存里的范围
|
||||
* 与当前视图严格一致;页面永远按自己需要的日期去索引。
|
||||
*/
|
||||
export async function loadModule<T>(name: FbModuleName, parse: (json: string) => T,
|
||||
sink: (data: T, fromCache: boolean) => void,
|
||||
onNote?: (note: string) => void): Promise<void> {
|
||||
onNote?: (note: string) => void,
|
||||
pathOverride?: string): Promise<void> {
|
||||
|
||||
const path: string = FB_MODULE_PATH[name] !== undefined ? FB_MODULE_PATH[name] : '';
|
||||
const path: string = pathOverride !== undefined && pathOverride !== ''
|
||||
? pathOverride
|
||||
: (FB_MODULE_PATH[name] !== undefined ? FB_MODULE_PATH[name] : '');
|
||||
if (path === '') {
|
||||
hilog.warn(DOMAIN, TAG, '模块 %{public}s 没有配路径', name);
|
||||
return;
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
FbAccount, FbAnniversaryItem, FbBookItem, FbCalEvent, FbHomeData, FbKinChip,
|
||||
FbKinGraph, FbKinPerson, FbMark,
|
||||
FbLedgerBook, FbLedgerDetail, FbLedgerEntry, FbNoteItem, FbNotificationItem, FbResourceItem,
|
||||
FbTaskItem, FbTripItem, FbWeather, FbStat, FbMemoryItem,
|
||||
FbTaskItem, FbTripItem, FbWeather, FbStat, FbMemoryItem, FbRecentDay,
|
||||
} from '../model/Types';
|
||||
|
||||
// ============================================================
|
||||
@@ -156,6 +156,13 @@ interface RawMemory {
|
||||
years_ago?: number; n?: number;
|
||||
}
|
||||
|
||||
/** `recent_days` 的原始行(服务端 `Anniversary::upcoming()` 的输出形状) */
|
||||
interface RawRecentDay {
|
||||
id?: number; date?: string; title?: string; kind?: string;
|
||||
person_id?: number; source?: string; days?: number;
|
||||
age?: number | null; nth?: number | null;
|
||||
}
|
||||
|
||||
interface RawTaskBoard {
|
||||
id?: number; name?: string; color?: string; tasks?: RawTask[];
|
||||
}
|
||||
@@ -544,7 +551,7 @@ export function parseHome(json: string): FbHomeData {
|
||||
greeting: '', date: '', weekday: '', todayLabel: '', lunarShort: '', lunarLine: '',
|
||||
marks: [], weather: null,
|
||||
stat: { todayEvents: 0, doing: 0, todo: 0, overdue: 0 },
|
||||
memories: [],
|
||||
memories: [], recentDays: [], meAvatar: '',
|
||||
todayEvents: [], tasks: [], unread: 0,
|
||||
};
|
||||
if (root === null) {
|
||||
@@ -567,6 +574,8 @@ export function parseHome(json: string): FbHomeData {
|
||||
weather: parseWeather(root['weather']),
|
||||
stat: parseStat(root['stat']),
|
||||
memories: parseMemories(root['memories']),
|
||||
recentDays: parseRecentDays(root['recent_days']),
|
||||
meAvatar: s(root['me_avatar'] as string),
|
||||
todayEvents: parseCalRows(rawEvents),
|
||||
tasks: parseTaskRows(rawTasks),
|
||||
unread: n(root['unread'] as number),
|
||||
@@ -635,6 +644,39 @@ export function parseStat(v: Object | null | undefined): FbStat {
|
||||
*
|
||||
* ⚠️ 字段是 SQL 风格(`starts_at` / `years_ago`),端上要驼峰 —— 所以有这一层。
|
||||
*/
|
||||
/**
|
||||
* 🔴 v59(用户 2026-10-18):`recent_days` → 首页「最近纪念日」。
|
||||
*
|
||||
* ⚠️ `age` / `nth` 在服务端是 **null 或数字**(不是 0)——`n()` 会把 null 变成 0,
|
||||
* 而"没有周岁"和"0 岁"是两件事(0 岁的宝宝确实存在)⇒ 这里显式保留 -1
|
||||
* 表示"没有这个信息",端上据此**不显示**那一段。
|
||||
* ⚠️ **不再排序**:服务端已按"最近的在前"排好,端上再排一遍就会出现
|
||||
* "两端顺序不一致"那类问题(本项目在往年的今天上踩过一次)。
|
||||
*/
|
||||
export function parseRecentDays(v: Object | null | undefined): FbRecentDay[] {
|
||||
const rows: RawRecentDay[] = arr<RawRecentDay>(v as RawRecentDay[]);
|
||||
const out: FbRecentDay[] = [];
|
||||
for (const r of rows) {
|
||||
const title: string = s(r.title);
|
||||
if (title === '' || s(r.date) === '') {
|
||||
continue;
|
||||
}
|
||||
const it: FbRecentDay = {
|
||||
id: n(r.id),
|
||||
date: s(r.date),
|
||||
title: title,
|
||||
kind: s(r.kind),
|
||||
personId: n(r.person_id),
|
||||
source: s(r.source) !== '' ? s(r.source) : 'custom',
|
||||
days: n(r.days),
|
||||
age: r.age !== null && r.age !== undefined ? n(r.age) : -1,
|
||||
nth: r.nth !== null && r.nth !== undefined ? n(r.nth) : -1,
|
||||
};
|
||||
out.push(it);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function parseMemories(v: Object | null | undefined): FbMemoryItem[] {
|
||||
const rows: RawMemory[] = arr<RawMemory>(v as RawMemory[]);
|
||||
const out: FbMemoryItem[] = [];
|
||||
|
||||
@@ -62,9 +62,35 @@ interface FbMeDevice {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 v59:`/me` 里的**家庭**部分(含标语)。
|
||||
*
|
||||
* ⚠️ 全部可选:老服务端不给这些字段时,端上保留原值(不能把它们冲成空)。
|
||||
*/
|
||||
interface FbMeFamily {
|
||||
id?: number;
|
||||
name?: string;
|
||||
/** 🔴 v59:该显示的那句标语(服务端按 个人→家庭→默认 算好) */
|
||||
slogan?: string;
|
||||
}
|
||||
|
||||
interface FbMePayload {
|
||||
server_time?: string;
|
||||
device?: FbMeDevice;
|
||||
/** v59:身份(姓名 / 头像 / 角色 / 家庭名 / 标语) */
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
role?: string;
|
||||
family?: FbMeFamily;
|
||||
}
|
||||
|
||||
/** 一次身份刷新(由 Index 收到 SESSION_OK 后写进全局状态) */
|
||||
export interface FbMeIdentityResult {
|
||||
has: boolean;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
familyName: string;
|
||||
slogan: string;
|
||||
}
|
||||
|
||||
/** 一次校验的结果 */
|
||||
@@ -77,6 +103,11 @@ export interface FbSessionCheck {
|
||||
serverTime: string;
|
||||
/** 续期后的令牌到期时间(拿不到为空) */
|
||||
expiresAt: string;
|
||||
/**
|
||||
* v59:身份快照。**只有 SESSION_OK 时才有**(`has=false` 表示本次没拿到,
|
||||
* 调用方应保留原值、不要把它冲成空 —— 否则断网启动会把头像/标语清掉)。
|
||||
*/
|
||||
identity?: FbMeIdentityResult;
|
||||
}
|
||||
|
||||
class FbSessionImpl {
|
||||
@@ -191,7 +222,17 @@ class FbSessionImpl {
|
||||
await this.markVerified(ctx, st);
|
||||
const exp: string = (payload.device !== undefined && payload.device.expires_at !== null
|
||||
&& payload.device.expires_at !== undefined) ? payload.device.expires_at : '';
|
||||
return { state: SESSION_OK, reason: '', serverTime: st, expiresAt: exp };
|
||||
/* 🔴 v59:把身份快照一并带回(姓名 / 真头像 / 家庭名 / 标语)。
|
||||
⚠️ 不做单独请求:`/me` 本来每次启动就打一次,顺手带上最省一次往返。 */
|
||||
const fam: FbMeFamily | undefined = payload.family;
|
||||
const identity: FbMeIdentityResult = {
|
||||
has: true,
|
||||
name: payload.name !== undefined ? payload.name : '',
|
||||
avatarUrl: payload.avatar !== undefined ? payload.avatar : '',
|
||||
familyName: fam !== undefined && fam.name !== undefined ? fam.name : '',
|
||||
slogan: fam !== undefined && fam.slogan !== undefined ? fam.slogan : '',
|
||||
};
|
||||
return { state: SESSION_OK, reason: '', serverTime: st, expiresAt: exp, identity: identity };
|
||||
}
|
||||
|
||||
if (r.status === 401) {
|
||||
|
||||
Reference in New Issue
Block a user