v0.8.0 本地加密 / 被踢清数据 / 增量同步 / 附件队列 / 顶栏同步进度
用户 2026-10-18 两条: 「直接做 L3 锁界面 + 本地加密(HUKS,踢设备即吊销密钥):数据在但读不出, 重登后恢复密钥即可用。」 「右上角头像和提醒前面加同步进度…首次全量、之后增量;进来先展示本地数据, 一边同步一边更新;附件后台队列同步(名册头像 / 电影海报 / 观影照片 / 资源附件)。」 ① 整库加密(FbSchema v4→v5,新增 module_sync / attach_cache 两张表) RDB encrypt=true。加密属性一经创建不可修改 ⇒ openWithEncryption 三段式: 直接开 → 失败删库重建 → 再失败降级明文(degraded 字段,设置页如实显示, 不静默降级)。 ② 两层密钥(FbKeyStore) HUKS 里 KEK(fb_kek_v1,不可导出)包裹 32 字节 DEK;revoke() 删 KEK ⇒ 已落盘附件密文在数学上解不开,这就是"踢设备即吊销密钥"。 重新登录重新生成 DEK 并重下附件(不做服务端托管,为了省一次重下 把能解你本地数据的密钥放到服务端不值)。 会话属性里 PURPOSE 只出现一次:原来把含 ENCRYPT|DECRYPT 的生成属性直接 拿去开会话再 push 一个 PURPOSE,同一 tag 出现两次在 HUKS 里行为未定义 ⇒ 拆成 kekBaseProps / kekGenProps / kekSessionProps。 ③ AES-256-GCM(FbCrypto) 落盘 iv(12)|ct|tag(16);变换串必须带 padding 段(AES256|GCM|PKCS7, 缺了抛 801);解密从尾部切 tag。 ④ 附件后台队列(FbAttachQueue) attach_cache 台账(pending/done/fail,MAX_TRIES=3);密文放 filesDir、 明文临时放 cacheDir;扩展名白名单筛 key。 图片地址收成统一入口 fbImageSrc(key,token):本地有就用 file://, 没有才回网络 —— 收成一个函数才漏不掉(5 个页面全部改过来)。 ⑤ 被踢 / 退出清数据 lockOut → 清令牌 → FB_ATTACH.wipe(第一步就是吊销 KEK)→ FbDb.wipe (先关句柄再 deleteRdbStore,并发去重,open 会等它)→ 同步状态复位。 重新登录后重建密钥 + 库 + 附件索引,否则之后只读网络不写缓存且不报错。 ⑥ 增量同步(FbSyncAll 重写) 先问 /api/v1/changes;指纹相同就跳过。本地没水位必须拉(否则老用户升级后 一个模块都不同步);24h 陈旧上限兜"改无 updated_at 表的已有行"; /changes 拿不到时判 ping status>0 —— 断网直接收工,不退回全量 (否则 12 个模块各等 15 秒)。 家谱也进了预同步列表(原来只在打开时拉,离线是一片空白); 总步数由 CACHE_MODULES.length 算出,不写死。 ⑦ 删除传播 服务端是物理删除、没有墓碑 ⇒ 观影同步改成"先清后插" (persons 跨模块共用,不清)。 ⑧ 顶栏同步进度 头像与铃铛之间加三态按钮(转圈 / 红感叹号 / 灰对勾)+ bindSheet 面板: 进度条、「N 个模块没有变化」、附件行、失败清单、立即同步(防连点)。 状态中心 FbSyncState(@ObservedV2 + @Trace,AppStorageV2 单例); 界面直接读 @Trace 字段,不提供派生 getter。 验证:hvigor BUILD SUCCESSFUL;_fb_arkts_guard.js 290/0(本批新增 59 条); _fb_v58_falsify.py 31 项注入 38/0;b57 反向验证 12/0(锚点跟上本批 logout 改动); 本地 e2e 1519/0;全量流水线 PUSH_RC=0。
This commit is contained in:
@@ -38,6 +38,10 @@ import { FB_SERVER_STORE } from '../data/FbServerStore';
|
||||
import { FB_DEVICE_STORE } from '../data/FbDeviceStore';
|
||||
import { FB_SESSION, SESSION_NEED_LOGIN, SESSION_OFFLINE } from '../data/FbSession';
|
||||
import { fbOnUnauthorized } from '../data/FbHttp';
|
||||
import { FbDb } from '../data/FbDb';
|
||||
import { FB_ATTACH } from '../data/FbAttachQueue';
|
||||
import { FB_KEY_STORE } from '../data/FbKeyStore';
|
||||
import { fbSyncState } from '../common/FbSyncStore';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
|
||||
/**
|
||||
@@ -165,10 +169,11 @@ struct Index {
|
||||
|
||||
const r = await FB_SESSION.check(ctx);
|
||||
if (r.state === SESSION_NEED_LOGIN) {
|
||||
this.app.lockReason = r.reason;
|
||||
this.app.loginStartStep = 2;
|
||||
this.app.needLogin = true;
|
||||
this.app.offlineLocked = false;
|
||||
/* 🔴 v58:有令牌却校验失败(被网页端踢掉 / 令牌过期 / 账号停用)
|
||||
⇒ 走 `lockOut`,它会**把本地数据一起清掉**。
|
||||
⚠️ 不能只设 needLogin 就完事:那样沙箱里的库和附件还在,
|
||||
与用户要的"所有内容都不允许看"差一大截。 */
|
||||
this.lockOut(r.reason);
|
||||
this.entered = false;
|
||||
} else if (r.state === SESSION_OFFLINE) {
|
||||
/* 离线超过 24 小时:**锁界面但不删令牌**。
|
||||
@@ -268,35 +273,76 @@ struct Index {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 🔴 v57:令牌失效(任何请求 401)→ 立刻锁定回登录页。
|
||||
* 🔴 v57/v58:令牌失效(任何请求 401 / 启动校验发现被踢)→ 锁定回登录页
|
||||
* **并把本地数据一起清掉**。
|
||||
*
|
||||
* 用户原话:「运行期间任何一个请求返回 401,立刻锁定跳登录页 ——
|
||||
* 这是真正的兜底,因为同步/写入本来就在不断发请求。」
|
||||
* v58 又加了一句:「退到登录页面之后,这里面所有的资源、所有的内容
|
||||
* 都是不允许看的。目的就是为了保证数据的安全性。」
|
||||
* ⇒ 只锁界面挡不住"拷走沙箱"。所以四处一起做:
|
||||
* 令牌 → 数据密钥 → 本地库 → 附件缓存。缺任何一处,那条路都还通。
|
||||
*
|
||||
* ⚠️ 由 `FbHttp` 的网络层统一回调(不是每个页面自己判),
|
||||
* 所以**漏不掉**;登录接口自己传了 `authProbe` 不会触发。
|
||||
* ⚠️ 由 `FbHttp` 的网络层统一回调(不是每个页面自己判),所以**漏不掉**;
|
||||
* 登录接口自己传了 `authProbe` 不会触发。
|
||||
*
|
||||
* @param reason 给用户看的原因('' = 用默认文案)
|
||||
*/
|
||||
private lockOut(): void {
|
||||
private lockOut(reason?: string): void {
|
||||
if (this.app.needLogin) {
|
||||
return; // 已经在登录页了,别反复设状态(会让界面闪)
|
||||
}
|
||||
const ctx: common.Context | undefined = this.abilityCtx();
|
||||
if (ctx !== undefined) {
|
||||
// ⚠️ 先清本地令牌(主防线:捡到手机的人拿不到它),再通知界面
|
||||
// ⚠️ 先清本地令牌(主防线:捡到手机的人拿不到它),再清数据
|
||||
FB_TOKEN_STORE.clear(ctx).catch((err: BusinessError): void => {
|
||||
hilog.warn(0x0000, 'FamilyBook', '清令牌失败:%{public}s', JSON.stringify(err));
|
||||
});
|
||||
FB_SESSION.clear(ctx).catch((err: BusinessError): void => {
|
||||
hilog.warn(0x0000, 'FamilyBook', '清会话记录失败:%{public}s', JSON.stringify(err));
|
||||
});
|
||||
this.wipeLocalData(ctx);
|
||||
}
|
||||
this.app.lockReason = '这台设备已经被退出登录,请重新登录';
|
||||
this.app.lockReason = reason !== undefined && reason !== ''
|
||||
? reason : '这台设备已经被退出登录,请重新登录';
|
||||
this.app.loginStartStep = 2;
|
||||
this.app.needLogin = true;
|
||||
this.app.offlineLocked = false;
|
||||
this.entered = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空本地数据(与设置页的「退出登录」走**同一套**动作)。
|
||||
*
|
||||
* ⚠️ 为什么不在 `FbSession` 里做:会话层只管"能不能用"这个判定,
|
||||
* 清数据要碰库、碰文件、碰 UI 状态,把它塞进会话层会让那层不再纯粹。
|
||||
* 而 `FbSession` 的两个返回值(need_login / offline)最终都汇到 `Index`,
|
||||
* 在这里统一编排刚好只写一份。
|
||||
*/
|
||||
private wipeLocalData(ctx: common.Context): void {
|
||||
this.doWipe(ctx).catch((err: BusinessError): void => {
|
||||
hilog.warn(0x0000, 'FamilyBook', '清本地数据异常:%{public}s', JSON.stringify(err));
|
||||
});
|
||||
}
|
||||
|
||||
/** 实际动作(拆出来是为了能 await 又能统一兜异常) */
|
||||
private async doWipe(ctx: common.Context): Promise<void> {
|
||||
// ① 附件(内部第一步就是**吊销数据密钥** → 已落盘的密文永久解不开)
|
||||
try {
|
||||
await FB_ATTACH.wipe(ctx);
|
||||
} catch (err) {
|
||||
hilog.warn(0x0000, 'FamilyBook', '清附件失败:%{public}s', JSON.stringify(err));
|
||||
}
|
||||
// ② 本地库
|
||||
try {
|
||||
await FbDb.inst().wipe(ctx);
|
||||
} catch (err) {
|
||||
hilog.warn(0x0000, 'FamilyBook', '清库失败:%{public}s', JSON.stringify(err));
|
||||
}
|
||||
// ③ 同步进度(别把上一账号的状态带到下一个)
|
||||
fbSyncState().reset();
|
||||
}
|
||||
|
||||
/** 登录成功:进主界面(会话信息已由登录页写进 `app.session`) */
|
||||
private onLoggedIn(): void {
|
||||
this.app.session.loggedIn = true;
|
||||
@@ -307,6 +353,52 @@ struct Index {
|
||||
this.app.lockReason = '';
|
||||
this.app.offlineLocked = false;
|
||||
this.entered = true;
|
||||
// 🔴 v58:重新登录后**必须把本地库重新打开**。
|
||||
// 被踢/退出时 `doWipe()` 把库删了(store=null),如果不在这里重开,
|
||||
// 整个应用之后都只能"读网络、不写缓存"—— 界面能用,但一断网就全空,
|
||||
// 而且没有任何报错(`isReady()` 只是返回 false)。这是最难查的那类问题。
|
||||
this.reopenLocal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新建立本地库 + 附件索引(登录成功后调)。
|
||||
*
|
||||
* ⚠️ 不 await:开库是异步的,挡住"进入首页"会让登录后多转圈半秒;
|
||||
* 各页面在库没好时会自动退化成"只走网络",不会出错。
|
||||
* ⚠️ 顺序:先开库,再让附件队列重建索引(它要读 `attach_cache`)。
|
||||
*/
|
||||
private reopenLocal(): void {
|
||||
const ctx: common.Context | undefined = this.abilityCtx();
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
this.doReopen(ctx).catch((err: BusinessError): void => {
|
||||
hilog.warn(0x0000, 'FamilyBook', '重建本地环境异常:%{public}s', JSON.stringify(err));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 三段:① 数据密钥(wipe 时被吊销了)② 本地库(wipe 时被删了)③ 附件索引(要读库)。
|
||||
*
|
||||
* ⚠️ 每段**各自兜异常**:一个环节失败不该让后面全都不做
|
||||
* (比如 HUKS 不可用时,库和附件索引仍然要能建起来)。
|
||||
*/
|
||||
private async doReopen(ctx: common.Context): Promise<void> {
|
||||
try {
|
||||
await FB_KEY_STORE.init(ctx);
|
||||
} catch (err) {
|
||||
hilog.warn(0x0000, 'FamilyBook', '重建数据密钥失败:%{public}s', JSON.stringify(err));
|
||||
}
|
||||
try {
|
||||
await FbDb.inst().open(ctx);
|
||||
} catch (err) {
|
||||
hilog.warn(0x0000, 'FamilyBook', '重开本地库失败:%{public}s', JSON.stringify(err));
|
||||
}
|
||||
try {
|
||||
await FB_ATTACH.init(ctx);
|
||||
} catch (err) {
|
||||
hilog.warn(0x0000, 'FamilyBook', '重建附件索引失败:%{public}s', JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,11 +34,11 @@ import { FbCinemaSheet } from '../../components/FbCinemaSheet';
|
||||
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
||||
import { FB_SET_MOVIES } from '../../model/FbSettingsMenus';
|
||||
import { FB_TOKEN_STORE } from '../../data/FbTokenStore';
|
||||
import { fbImageUrl } from '../../data/FbHttp';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbImageSrc } from '../../data/FbAttachQueue';
|
||||
|
||||
const DOMAIN: number = 0x0000;
|
||||
const TAG: string = 'FbMoviesPage';
|
||||
@@ -554,7 +554,7 @@ export struct FbMoviesPage {
|
||||
if (mv.posterKey !== '' && !this.posterFailed(mv.id)) {
|
||||
// 🔴 真海报:走服务端 `media.php` 代理,令牌用 URL query 带过去
|
||||
// (`Image()` 无法加自定义请求头,见 fbImageUrl 的说明)。
|
||||
Image(fbImageUrl(mv.posterKey, FB_TOKEN_STORE.get()))
|
||||
Image(fbImageSrc(mv.posterKey, FB_TOKEN_STORE.get()))
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.objectFit(ImageFit.Cover)
|
||||
|
||||
@@ -22,7 +22,6 @@ import { FbPersonSheet } from '../../components/FbPersonSheet';
|
||||
import { FbPersonEditSheet } from '../../components/FbPersonEditSheet';
|
||||
import { FbKinGraphView } from '../../components/FbKinGraph';
|
||||
import { FbTagChip } from '../../components/FbTagChip';
|
||||
import { fbImageUrl } from '../../data/FbHttp';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { FbDb } from '../../data/FbDb';
|
||||
import { FbPersonRepo, FbRosterPerson, FbRosterSyncResult, FbTagCount } from '../../data/FbPersonRepo';
|
||||
@@ -32,6 +31,7 @@ import { call } from '@kit.TelephonyKit';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
|
||||
import { fbImageSrc } from '../../data/FbAttachQueue';
|
||||
|
||||
const DOMAIN: number = 0x0000;
|
||||
const TAG: string = 'FbPeoplePage';
|
||||
@@ -838,7 +838,7 @@ export struct FbPeoplePage {
|
||||
color: p.avatarColor,
|
||||
dia: 40,
|
||||
fontSizeVp: 16,
|
||||
imageUrl: fbImageUrl(p.avatar, FB_TOKEN_STORE.get())
|
||||
imageUrl: fbImageSrc(p.avatar, FB_TOKEN_STORE.get())
|
||||
})
|
||||
|
||||
Column({ space: 3 }) {
|
||||
|
||||
@@ -17,7 +17,11 @@ import { FbAvatar, FbDot, FbPrimaryButton, FbTag } from '../../components/Widget
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { FB_TOKEN_STORE } from '../../data/FbTokenStore';
|
||||
import { FB_SERVER_STORE } from '../../data/FbServerStore';
|
||||
import { FB_SYNC_ALL_PROGRESS, FbSyncAllResult, syncAll } from '../../data/FbSyncAll';
|
||||
import { FbSyncAllResult, syncAll } from '../../data/FbSyncAll';
|
||||
import { FbSyncState, fbSyncState } from '../../common/FbSyncStore';
|
||||
import { FbDb } from '../../data/FbDb';
|
||||
import { FB_ATTACH } from '../../data/FbAttachQueue';
|
||||
import { FB_KEY_STORE } from '../../data/FbKeyStore';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
@@ -104,8 +108,13 @@ export struct FbSettingsPage {
|
||||
this.tokenNote = '正在同步…';
|
||||
// 实时进度(定时器把当前步骤刷到界面上)。
|
||||
// ⚠️ 本机 hilog 读不出内容 ⇒ 不给实时进度,用户会以为卡死。
|
||||
// ⚠️ v59 起进度在 `fbSyncState()` 里(顶栏图标也订阅同一份),
|
||||
// 不再读 `FbSyncAll` 的模块级字符串 —— 那种 `export let` 的"实时值"
|
||||
// 在打包器内联之后就停住不动了(详见 FbSyncStore 的说明)。
|
||||
const timer: number = setInterval((): void => {
|
||||
this.tokenNote = '同步中…(' + FB_SYNC_ALL_PROGRESS + ')';
|
||||
const s: FbSyncState = fbSyncState();
|
||||
this.tokenNote = '同步中…(' + s.current + ' '
|
||||
+ s.done.toString() + '/' + s.total.toString() + ')';
|
||||
}, 700);
|
||||
const res: FbSyncAllResult = await syncAll();
|
||||
clearInterval(timer);
|
||||
@@ -113,11 +122,20 @@ export struct FbSettingsPage {
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录:清令牌 + 回登录页(**地址保留**)。
|
||||
* 退出登录:清令牌 + 清本地数据 + 回登录页(**地址保留**)。
|
||||
*
|
||||
* 🔴 为什么不顺手清本地数据:本地优先架构下"没登录"不等于"没数据"。
|
||||
* 用户可能只是在别人手机上登出、或暂时断网 —— 把已同步的数据一起删掉,
|
||||
* 等于让他下次进来面对一片空白(还得重新全量同步)。
|
||||
* 🔴 v58 改了这里的行为(用户 2026-10-17):
|
||||
* 「退到登录页面之后,这里面所有的资源、所有的内容都是不允许看的。
|
||||
* 目的就是为了保证数据的安全性。」
|
||||
* ⇒ 退出登录 = **主动清空**本地库 + 附件 + 数据密钥。
|
||||
* 之前那句"不顺手清本地数据、否则下次面对一片空白"的顾虑,
|
||||
* 在**重新登录会自动全量同步**之后已经不成立 —— 用户下次进来
|
||||
* 看到的是"正在同步",而不是空白。
|
||||
*
|
||||
* ⚠️ 顺序:**先清令牌**(主防线,捡到手机的人拿不到),再清数据,最后才尽力通知服务端。
|
||||
* 通知失败不报错:它只是让这台设备从列表里消失得更快,删不掉就等 24h 自然过期。
|
||||
* ⚠️ 附件清理里已经包含了"吊销数据密钥"(`FB_ATTACH.wipe` 的第一步),
|
||||
* 所以这里不必再单独调一次 `FB_KEY_STORE.revoke`。
|
||||
*/
|
||||
private async logout(): Promise<void> {
|
||||
const ctx: common.Context | undefined = this.getUIContext().getHostContext();
|
||||
@@ -125,15 +143,13 @@ export struct FbSettingsPage {
|
||||
this.tokenNote = '拿不到应用上下文,无法退出';
|
||||
return;
|
||||
}
|
||||
/* 🔴 v57(用户 2026-10-17):「先删本地令牌(这是主防线,捡到手机的人拿不到)
|
||||
→ 再尽力通知服务端」。
|
||||
⚠️ 顺序不能反:离线时通知发不出去,如果等它成功再清本地,
|
||||
用户会看到"点了退出却没退"。本地那一刀是唯一可靠的。
|
||||
⚠️ 服务端那一条**失败也不报错**:它只是让"这台设备"从设备列表里消失得
|
||||
更及时,删不掉就等它 24h 后自然过期,或用户在网页端手动踢。 */
|
||||
const token: string = FB_TOKEN_STORE.get();
|
||||
// ① 令牌(主防线)
|
||||
await FB_TOKEN_STORE.clear(ctx);
|
||||
await FB_SESSION.clear(ctx);
|
||||
// ② 本地数据:库 + 附件(含吊销密钥)+ 同步进度
|
||||
await this.wipeLocalData(ctx);
|
||||
// ③ 尽力通知服务端
|
||||
if (token !== '') {
|
||||
fbApi('/api/v1/logout', { method: 'POST', token: token, authProbe: true })
|
||||
.catch((err: BusinessError): void => {
|
||||
@@ -148,6 +164,30 @@ export struct FbSettingsPage {
|
||||
this.app.needLogin = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 清空本地数据(退出登录 / 被远程序号移除**共用**这一份)。
|
||||
*
|
||||
* 三件事一起做才算"读不出":
|
||||
* ① 吊销数据密钥(`FB_ATTACH.wipe` 的第一步)→ 已落盘的附件密文永久解不开;
|
||||
* ② 删库(RDB 整库加密,删掉即失去密钥意义);
|
||||
* ③ 复位同步进度(别把上一账号的"上次同步时间"带给下一个账号)。
|
||||
*
|
||||
* ⚠️ 失败**不抛**:无论如何都要回登录页 —— 那才是用户的主诉求。
|
||||
*/
|
||||
private async wipeLocalData(ctx: common.Context): Promise<void> {
|
||||
try {
|
||||
await FB_ATTACH.wipe(ctx);
|
||||
} catch (err) {
|
||||
hilog.warn(DOMAIN, TAG, '清附件失败:%{public}s', JSON.stringify(err));
|
||||
}
|
||||
try {
|
||||
await FbDb.inst().wipe(ctx);
|
||||
} catch (err) {
|
||||
hilog.warn(DOMAIN, TAG, '清库失败:%{public}s', JSON.stringify(err));
|
||||
}
|
||||
fbSyncState().reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更换服务器:回登录页第一步重填地址。
|
||||
*
|
||||
@@ -439,6 +479,19 @@ export struct FbSettingsPage {
|
||||
用户 2026-10-17:「只有 uuid 用户无法确定是哪台设备。」
|
||||
⇒ 网页端「设备管理」里显示的就是这个名字,用户要能对上号。 */
|
||||
this.plainRow('这台设备', FB_DEVICE_STORE.getDeviceName())
|
||||
/* 🔴 v58:本机数据到底加没加密 —— 必须**如实显示**。
|
||||
用户 2026-10-17 定的 L3 是"数据在但读不出",
|
||||
而代码里有一条"HUKS 不可用时降级明文"的兜底(见 FbKeyStore)。
|
||||
那条兜底如果不显示出来,用户会一直以为自己的数据是加密的 ——
|
||||
这比不加密更糟(虚假的安全感)。
|
||||
⚠️ 这两个值在打开设置页时才读一次(不是 @Trace),
|
||||
所以放在 build 里直接算:库/密钥状态在进入设置页后基本不会变。 */
|
||||
this.plainRow('本机数据', FbDb.inst().isReady()
|
||||
? (FbDb.inst().isEncrypted() ? '已加密' : '未加密(本机不支持加密)')
|
||||
: '准备中')
|
||||
this.plainRow('附件密钥', FB_KEY_STORE.isWeak()
|
||||
? '已降级(明文保存)'
|
||||
: (FB_KEY_STORE.dek() !== null ? 'HUKS 保护中' : '未建立'))
|
||||
}
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
|
||||
Reference in New Issue
Block a user