UDMF × ArkTS:碰一碰分享卡片字段白名单与预检拦截【鸿蒙心迹】
跨设备分享最容易被忽略的时刻,不是用户碰到目标设备之后,而是把数据交给分享能力之前。活动卡片里可能有联系人电话、内部备注、访客邮箱和公开活动名称;如果把整个对象直接序列化,传出去的未必只是用户屏幕上看到的那一小段摘要。这篇把关注点放在碰一碰分享链路的发送前预检。Demo 叫 TouchCardGuard,任务 SHARE_20261017_06。它不会伪造设备发现、NFC 触发或跨端落点回调,而是在任何实际传输之前先准备一份可审阅的 UDMF 纯文本对象。只有经过字段白名单、URL 校验、UTF-8 大小限制和编辑代次检查,界面才会显示 PREFLIGHT_READY。真正的碰一碰传输仍需按设备和系统版本另行接入。
## 一、分享草稿与分享负载不是同一个对象活动草稿里有五项字段:活动编号、标题、链接、联系手机和访客邮箱。用户点击“分享”并不意味着这些字段都获得了传输授权。我的默认策略是严格列举能离开本应用的三个字段:eventId、title、url。其他字段没有进入序列化结构的机会。这里的“剥离两个字段”是 Demo 对草稿字段数的统计,不表示某个系统隐私 API 已经执行了脱敏。相比拿整段 JSON 做手机号正则替换,结构化白名单更可靠。自由文本里可能存在电话、地址和临时令牌,正则处理很难覆盖全部变形。这里不允许自由备注进入有效载荷;若未来增加说明文本,必须重新评估敏感信息识别和用户确认流程。URL 也不能仅使用 startsWith('https://') 通过:恶意链接可以把可信域名放进路径或子域名,看似相似,却导向完全不同的站点。演示使用保留示例域 events.example.org,事件号固定 EVT_06,公开标题固定“鸿蒙体验日”,链接是 https://events.example.org/e/06。这些都不是实际活动地址。联系手机与访客邮箱刻意只保留在输入模型,最终序列化的文本不包含它们。相比“测试传完后再查”,预览白名单更方便审计。## 二、先构建安全摘要,再创建统一数据对象第一段代码解决“业务字段意外外泄”和“链接范围过宽”。注意我用完整的正则匹配一个固定示例路由,而不是假设某个未核实的浏览器解析 API 在 ArkTS 中与 Node.js 行为完全一致。生产项目的允许域和路径规则应该由可信配置给出,并做好大小写、国际化域名和重定向检查。arktsinterface CardDraft { eventId: string; title: string; url: string; contactPhone: string; guestEmail: string;}function makeSafeSummary(draft: CardDraft): string { const eventId = draft.eventId.trim(); const title = draft.title.trim(); const url = draft.url.trim(); if (!/^EVT_[0-9]{2}$/.test(eventId) || title.length < 1 || title.length > 40) { throw new Error('CARD_FIELDS_INVALID'); } if (!/^https:\/\/events\.example\.org\/e\/[0-9]{2}$/.test(url)) { throw new Error('URL_NOT_ALLOWED'); } return JSON.stringify({ type: 'share_card', eventId, title, url });}序列化时明确写出四个字段,contactPhone、guestEmail 根本不会被读出来。这里的 type 是业务识别字面值,不是 UDMF 的系统 MIME 类型。系统的统一数据类型仍由下一段代码的 PlainText 决定。标题字符上限是业务约束,不是 HarmonyOS 传输能力上限;如果包含表情符号,JavaScript 的字符串长度与最终字节数也不同。## 三、字节预算必须在 UTF-8 编码之后计算我把应用级预算设置为 256 字节。它是产品为活动摘要自定的阈值,不是华为“碰一碰最大载荷”的官方规格。原因是避免大型备注或意外膨胀的字段被塞入分享摘要,同时便于日志只记录字节数而不输出内容。UDMF 是数据描述容器,不能因为构建成功就推断设备已经完成传输。第二段代码以华为官方文档中的 unifiedDataChannel.UnifiedData 和 PlainText 为基础,使用 ArkTS util.TextEncoder 计算 UTF-8 字节数。PlainText.textContent 是最终能够给接收端读取的公开摘要,abstract 则是简短可展示说明。arktsimport { unifiedDataChannel } from '@kit.ArkData';import { util } from '@kit.ArkTS';function createSharePayload(summary: string): unifiedDataChannel.UnifiedData { const encoded: Uint8Array = new util.TextEncoder('utf-8').encodeInto(summary); if (encoded.length > 256) { throw new Error('PAYLOAD_TOO_LARGE'); } const record = new unifiedDataChannel.PlainText(); record.textContent = summary; record.abstract = '活动分享卡片'; const data = new unifiedDataChannel.UnifiedData(); data.addRecord(record); return data;}````encodeInto()` 的返回类型需要根据项目锁定的 SDK 类型定义检查,示例使用华为 Kit 文档中的写法。它返回的是编码后的字节数组,这一点与浏览器 `TextEncoder.encodeInto(input, dest)` 不同,不能机械照抄 Web 示例。若你使用的是较旧的 SDK,请以本机 SDK 的声明文件为准,而不是把不存在的重载塞进代码里。实际传输可能还有数据封装开销,本篇预算只针对编码后的 JSON 内容。## 四、预检通过还不等于此刻可以发送另一个边界来自编辑时序。用户预览了活动卡片,页面显示“可分享”;紧接着把活动链接改了,或者切换了另一张卡片。旧预览继续保持绿色会带来两个问题:它可能分享旧版本,也可能让人误以为刚编辑的内容已经审查过。因此需要把预检和草稿修订号绑定。下面的门禁类不做任何跨设备调用。业务层每次编辑调用 `changed()`,预检完成后保存当前修订号;提交前再对照。离开页面则调用 `dispose()`,让仍在异步链路中的旧预检失去资格。若日后接入系统触发回调,回调第一件事仍应重新检查修订号。arktsclass SharePreflightGate { private revision: number = 4; private checkedRevision: number = -1; private active: boolean = true; changed(): void { this.revision++; this.checkedRevision = -1; } checked(): void { if (!this.active) { throw new Error(‘PAGE_CLOSED’); } this.checkedRevision = this.revision; } canContinue(): boolean { return this.active && this.checkedRevision === this.revision; } dispose(): void { this.active = false; this.checkedRevision = -1; }}````revision=4是本次演示给出的当前草稿号;它不是系统 UDMF 属性。必须明确“通过预检”与“已经发送”是两个独立状态。页面上可以显示PREFLIGHT_READY,但不应在没有真实目标设备的情况下写成 SENT。还要让用户可以主动查看最终序列化文本,而不是只有绿色对勾和一个黑盒确认按钮。## 五、演示检查单应围绕拒绝路径设计为了核对文图,本轮固定可见数据是任务 SHARE_20261017_06、活动号 EVT_06、当前修订 4、剥离私有字段 2、应用预算 256B、状态 PREFLIGHT_READY。示意页面保留“预览安全摘要”和“重新检查”两个操作,不出现“传输完成”。接收端尚未实现,所以不能把该画面称为设备配对成功截图。要验证算法,先给一个正常草稿,检查最终 JSON 中没有 contactPhone或guestEmail;再把 URL 改成 https://events.example.org.evil.test/e/06,它应该被规则拒绝。随后给标题加入足量 Unicode 字符,确保大小限制按 UTF-8 字节而不是按字符数判断。最后在预检结束后修改标题,canContinue()必须返回false。这几个用例比单纯测试成功按钮更贴近工程风险。本篇 DevEco 和手机图均为静态演示设计,HiLog 中的 ALLOWLIST_OK、PREFLIGHT_READY 也是预期业务日志,不冒充真实设备记录。尤其不能用“右侧模拟器出现一个按钮”证明精准碰一碰已接入。真实接入还依赖设备能力、前后台条件、用户授权、目标应用处理及系统传输协议,应按照相应官方指南继续核对。## 六、真正需要收住的边界还需要考虑被分享的数据处于什么保留周期。预检阶段可以把文本留在页面内存中,但不建议为了“下次省一步”就把联系方式原文、预检 JSON 与接收目标一起落盘。若用户切换账号、活动撤销或页面退出,旧预览应同步失效;已取得的统一数据对象不再复用。产品日志只记录失败原因、任务号和载荷字节,不记录邮箱、手机号或完整地址。对于公开链接,还应当由业务后端验证事件是否仍有效;本地白名单无法防止内容本身过期。一份 UDMF 数据的构建成功,最多说明在代码里形成了符合类型要求的数据记录;**它不能承诺对端接收、精准落点或跨设备回调**。本篇完成的是一个独立可复用的分享预检模块:白名单使非公开字段不进入载荷,严格链接校验缩窄目标范围,UTF-8 预算防止摘要超出产品限制,修订号防止旧预览被当成新内容。后续的系统碰一碰适配应从这里接收已经核验的对象,而不是绕过校验重新序列化原始草稿。参考:华为官方 UDMF/剪贴板的统一数据示例(UnifiedData、PlainText、addRecord`):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-v5/use_pasteboard_to_copy_and_paste-V5 ;碰一碰场景介绍:https://developer.huawei.com/consumer/cn/features/all-scenario 。示例只实现本地预检,未在 DevEco 中实际编译或进行碰一碰设备测试。
更多推荐



所有评论(0)