Memories / 文档

本页目录

Memories

Memories 的 Android/iOS 客户端。Flutter UI 通过 crates/ffi/memories.h 调用共享的 Rust 服务;不会实现独立的存储或密码学协议。

选择模式

首次启动时,包括从没有模式偏好设置的版本升级后的首次启动,请选择工作位置:

模式 数据位置 所需条件
独立模式 此手机上的加密资料库;可离线使用 创建本地资料库或解锁现有资料库
Center 自托管服务器的托管存储 服务器源地址,如 https://center.example.com,以及成员令牌
SaaS https://memories.ivjn.us 的托管存储 该服务的成员令牌;URL 固定

应用的 Center 模式指启用了托管存储的服务器(memories-center --data DIR)。 它不同于只负责票据授权的 Center,后者为本地设备分发签名票据。 Center 和 SaaS 使用同一托管客户端;区别在于端点选择和保存的连接配置。 两者均不要求先创建或解锁本地资料库。

首次连接

  1. 选择模式。应用会记住该选择,在下次启动时使用。
  2. 对于独立模式,创建或解锁资料库。对于 Center,输入不含 API 路径的服务器 HTTPS 源地址; 对于 SaaS,保留固定的服务地址。
  3. 在任一托管模式中,输入服务运营者提供的成员令牌,并选择连接。 成员的角色和范围决定其可执行的操作;内容工作请使用成员令牌,不要使用运营者管理员令牌。
  4. 连接后,打开托管知识,浏览、搜索、查看、编辑或删除知识。 还可以审查请求或打开已配对设备。服务器地址、当前保险库和授予的范围始终可见。

托管内容使用运营者持有的密码进行静态加密,但运营者可以读取它。 应用会显示服务器可读取。设备间的 封装秘密 具有独立的端到端保护边界。

切换、断开与重新连接

登录前后均可使用主工具栏中的切换模式。对话框会标记当前模式并解释影响; 选择其他模式以切换,或选择取消以保留会话。 请先完成或离开已打开的编辑器;离开未保存的草稿时会要求确认。

操作 结果
切换模式 锁定本地资料库、停止共享、关闭活动的托管连接并清除可见内容;保存新模式
返回独立模式 重新打开处于锁定状态的本地资料库页面;现有数据保留
再次进入 Center 或 SaaS 若存在,则恢复该模式保存的连接
断开连接 移除当前配置保存的令牌并关闭会话;URL 和另一模式的配置保留
将应用置于后台 锁定本地访问、清除托管会话并关闭敏感页面和草稿
从后台返回 在托管页面使用重新连接已保存的会话,或输入令牌并再次连接

切换不会上传、复制、合并或删除资料库数据。断开连接不会在服务器上撤销令牌。 Center 和 SaaS 将各自的 URL/令牌对保存在不同的平台安全存储条目中。 升级时,旧托管连接只会导入匹配的模式;参见 配置与迁移。

可用功能

工作流 独立模式 Center / SaaS
本地资料库创建、备份与恢复 可用 使用托管服务;无需本地设置
浏览、搜索、查看、编辑和删除知识 解锁后可用 托管知识,操作受令牌范围限制
创建知识卡片 本地全页编辑器 托管全页编辑器,包含托管 API 接受的字段
审查托管授权请求 切换到 Center 或 SaaS 待处理/历史标签页、批准/拒绝确认和刷新
设备配对 本地设备/成员批准工作流 托管配对、封装秘密和设备控制,受权限限制
中继共享与云连接 通过本地资料库控制项使用 托管页面不提供

托管创建不包含标签、来源和关系;编辑现有普通卡片支持这些字段,并保留其他元数据。 普通记忆使用文本编辑器。结构化档案、受保护卡片、类型模板和 E2E 卡片在此通用编辑器中只能查看。 两个编辑器均会在保存失败时保留草稿;应用锁定或进入后台后,草稿不会保留。 参见内容编辑和授权。

连接故障排查

应用在保存连接前通过 /v1/session 验证令牌,即使账户只有写入权限也一样。 请与应用一起升级自托管服务器。只读账户可以浏览,但没有修改控件; 只写账户可以创建,无需加载资料库或类型列表。服务器会再次授权每项操作。 搜索排除私有内容、凭据和文件;清空查询以返回知识列表。 过期/撤销的会话、权限不足和服务器不可用均有本地化的错误反馈。 遇到 401 时需要断开连接,并使用有效令牌重新连接。

  • 无法连接: 检查完整的 HTTPS 源地址、网络、证书和令牌。 自托管 Center 必须启用托管存储;仅提供票据的服务无法支持此页面。 手机上的 localhost 就是手机本身。
  • 权限被拒绝: 与服务运营者检查令牌的保险库、角色、范围和有效期。 选择另一种模式不会授予访问权限。
  • 没有待处理请求: 刷新并检查请求者/保险库/审批者路由。 历史标签页不是完整的服务器审计日志。
  • 进入后台后会话关闭: 请显式重新连接。 未保存的草稿已在锁定时关闭,无法恢复。
  • 原生服务无法启动: 使用包含匹配 Rust 库的构建。 托管模式也使用 FFI,需要原生库。

开发

先从仓库根目录使用 scripts/build-android.ps1 或 scripts/build-ios.sh 构建原生库。工具链要求和签名说明参见根目录的 README。

使用与 CI 一致的 Flutter 3.44.2。在此目录运行:

flutter pub get
flutter gen-l10n
dart format --output=none --set-exit-if-changed lib test integration_test
flutter analyze
flutter test
flutter test integration_test/native_smoke_test.dart -d DEVICE_ID
flutter run -d DEVICE_ID

Widget 测试使用模拟后端,测试锁屏生命周期行为、模式选择/切换、托管会话隔离、 卡片、本地化和秘密引用流程。剪贴板测试模拟 Flutter 平台通道; 不会验证是否实际送达操作系统剪贴板。集成测试加载真实原生库, 并在设备上创建一次性数据库。正常运行使用平台应用支持目录,以及由 Keychain/Keystore 保护的本地因子。开发时请使用测试数据;当前信任边界参见 SECURITY.md。

文档秘密与剪贴板

在独立模式中,打开记忆/卡片菜单,选择保护文档中的秘密, 将当前正文中的精确值替换为命名引用。确认历史记录警告,输入引用名称和隐藏的秘密, 然后在提示时授权。Rust 服务在一个事务中存储凭据并替换正文。

在文档、受保护卡片或凭据上选择复制秘密,经确认和授权后复制其当前值。 文档会先显示引用选择器。OTP 卡片复制种子;其单独的 OTP 操作生成一次性代码。 值不会显示,进入后台或锁定后的延迟响应会被丢弃。 已完成的剪贴板复制可能在锁定后继续保留。

普通编辑会保留引用映射。旧文档版本和现有备份保留此前内容。 引用规则、更新、兼容性和剪贴板限制参见 wiki 秘密指南。 添加这些操作时,请同时重新构建原生库和 Flutter 应用。

本地化

使用应用栏中的语言图标,选择英语、简体中文、日语、德语、法语或系统默认语言。 偏好设置与加密资料库分开保存,锁定时也可使用。 切换语言会保留当前资料库会话。

翻译位于 lib/l10n/app_*.arb;flutter gen-l10n 生成被忽略的 lib/l10n/generated/ 目录。Material 和 Cupertino 控件使用 Flutter 的本地化委托。 添加消息或语言时,请遵循本地化指南。

共享会话(中继 RPC 主机)

在独立模式中,解锁且处于前台时,更多菜单提供共享会话。 它使用高熵令牌加入 wss://…/v1/rpc/{room}/a,使桌面 CLI 可以作为槽位 b 连接 (MEMORIES_DAEMON_RELAY / MEMORIES_DAEMON_ROOM / MEMORIES_DAEMON_TOKEN)。 进入后台或锁定会停止会话。MVP 在移动端没有 HTTP/WS 监听; 原生 FFI 拒绝列表仍然适用。参见远程守护进程。

生物识别解锁与敏感操作

在已解锁的独立模式资料库中,使用更多菜单 → 使用生物识别 / 设备锁解锁 (默认关闭;开启时需要一次系统检查)。lib/biometric.dart 封装 local_auth (Android 上使用 BiometricPrompt,并可回退到设备 PIN/图案/密码; iOS 上使用 Face ID / Touch ID,Info.plist 中包含 NSFaceIDUsageDescription)。 MainActivity 按照 local_auth 的要求继承 FlutterFragmentActivity。

  • 快速解锁的资料库密码使用 AndroidOptions.biometric(resetOnError: false) 存储: Android Keystore AES 密钥设置 setUserAuthenticationRequired(true) 和 setInvalidatedByBiometricEnrollment(true)。在 iOS 上,Keychain 条目使用 KeychainAccessibility.passcode。
  • 受验证限制的操作:解锁资料库、复制/显示凭据明文、使用受保护卡片 / OTP、 批准配对和 Hosted Center 请求、封装与重新封装。
  • 生物识别注册发生变化(KeyPermanentlyInvalidatedException)时,应用会告知用户, 通过设备凭据重新验证,只丢弃失效的快速解锁副本,并在一次密码解锁后重新创建密钥。 资料库绝不会被清除。
  • 没有生物识别/设备凭据,或开关关闭时:使用现有密码流程。
  • 测试模拟 DeviceAuth / UnlockSecretStore(test/biometric_test.dart)。

内容编辑器

lib/content_editor.dart 由新记忆、凭据和 Hosted Center 条目共用: 文件导入(从文件名获取标题)、针对 .env 类内容的等宽模式(自动检测或手动切换), 以及以内容为先的名称,名称从文件名或首个非空行推断,只有保存时仍为空才提示输入。 手写(笔/橡皮擦/撤销/清空,ML Kit Digital Ink)和 OCR(相机/照片/文件, ML Kit 文本识别)在设备上运行;参见手写与 OCR。 Widget 测试注入 ScanServices 模拟实现。

记忆和知识卡片会保持打开,直到保存成功。写入失败会保留草稿以供重试; 带着更改离开时会要求确认。草稿仅保存在内存中,进入后台或空闲锁定仍会将其关闭。 文件导入在替换现有文本前会询问。记忆在保存前显示其私有/普通设置, 附件重试会跳过本次编辑中已保存的文件。

知识卡片使用全页编辑器,提供持续可用的保存操作、自定义字段、标签、来源和关系。 编辑内容会打开共享文本编辑器,用于文件导入、手写和 OCR。 托管创建仅提供现有移动端 API 接受的元数据。

移动端授权

Center 和 SaaS 页面将待处理请求与历史记录分开。 确认批准或拒绝前,请审查请求者、保险库、访问范围、时长和完整理由。 批准仍使用现有设备身份验证关卡,请求者必须另行领取授权。 错误会保留请求以供重试;下拉刷新或使用刷新按钮。

Center 请求 API 不返回完整的决定历史。 历史标签页合并当前连接期间作出的决定与 Center 返回的已批准请求。 离开页面时会清除这些本地记录。失败的新连接会关闭,不会替换已保存的配置。

本地成员管理只显示已显示角色所允许的操作。确认对话框会标明成员及旧/新访问权限。 Rust 服务会重新检查当前策略;UI 筛选不会授予任何权限。 所有者成员身份不能通过普通角色操作更改,只有所有者能看到所有权转移。

品牌

标志源文件:assets/brand/logo.svg(以及 logo-mono.svg、自适应 logo-foreground.svg)。图标和启动画面使用 rsvg-convert 渲染; 标志更改时,请重新生成相同尺寸。