Skip to content

feat(amsg): client 新增 client_state 三方法,对接单用户 worker 云端状态镜像#23

Merged
Tosd0 merged 2 commits into
mainfrom
feat/amsg-client-state
Jul 17, 2026
Merged

feat(amsg): client 新增 client_state 三方法,对接单用户 worker 云端状态镜像#23
Tosd0 merged 2 commits into
mainfrom
feat/amsg-client-state

Conversation

@Tosd0

@Tosd0 Tosd0 commented Jul 17, 2026

Copy link
Copy Markdown
Owner

改了什么

@rei-standard/amsg-client 补上 /client-state 端点(amsg-server 2.6.0-next.3 已上线)的配套方法,前端不用再自己拼加密请求:

方法 语义
putClientState(entries) 批量 upsert。一次请求发完全部变更(照顾 iOS 切后台前只有几秒的存活窗口);请求体走既有加密链路(需先 init()),服务端按 updatedAt 最后写赢,重发旧批次无害
getClientState(namespace) 取一个 namespace 的全部条目,自动解密响应 envelope(同 listMessages
clearClientState() 清空该用户全部云端状态,给「清除云端状态」这类设置入口用

配了 serverToken 时三个方法都带 X-Client-Token

验证

  • 新增 test/client-state.test.mjs 6 个用例:请求形状(URL/method/加密头/token)、密文 body 不含明文 + 解密 roundtrip、加密响应解密、非 success 响应透传、未 init() 报错
  • client 全套测试 74/74 通过;tsup 构建与 d.ts 生成正常
  • changeset:@rei-standard/amsg-client minor

https://claude.ai/code/session_01W5FWFQSHixnqgLgVsLpMDp

putClientState 批量 upsert(走既有加密链路,一次请求发完变更)、
getClientState 按 namespace 取回并解密、clearClientState 全量清空。
配了 serverToken 时均带 X-Client-Token。

Claude-Session: https://claude.ai/code/session_01W5FWFQSHixnqgLgVsLpMDp

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces three new methods (putClientState, getClientState, and clearClientState) to the ReiClient class to support single-user cloud state mirroring via the /client-state endpoint, along with corresponding unit tests and a changeset. The review feedback recommends adding client-side validation for the input parameters of putClientState and getClientState to prevent unnecessary encryption overhead and invalid API requests, as well as adding unit tests to cover these validation checks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1015 to +1016
async putClientState(entries) {
const json = JSON.stringify({ entries });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在进行高开销的加密操作(_encrypt)和网络请求之前,建议在客户端对 entries 参数进行前置校验。如果 entries 未定义、不是数组或为空数组,JSON.stringify({ entries }) 会产生非预期的 JSON(例如 entriesundefined 时会变成 "{}"),且会白白消耗加密算力并发送注定失败的请求。

Suggested change
async putClientState(entries) {
const json = JSON.stringify({ entries });
async putClientState(entries) {
if (!Array.isArray(entries) || entries.length === 0) {
throw new TypeError('[rei-standard-amsg-client] entries must be a non-empty array');
}
const json = JSON.stringify({ entries });

Comment on lines +1045 to +1046
async getClientState(namespace) {
const res = await fetch(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议在客户端对 namespace 参数进行前置校验。如果 namespace 传入了非字符串或空字符串,encodeURIComponent(namespace) 会将其转换为 "undefined" 或空值,从而发送无效的请求。在客户端直接抛出 TypeError 可以避免不必要的网络开销。

  async getClientState(namespace) {
    if (typeof namespace !== 'string' || !namespace.trim()) {
      throw new TypeError('[rei-standard-amsg-client] namespace must be a non-empty string');
    }
    const res = await fetch(

Comment on lines +141 to +147
test('putClientState() before init() throws Not initialised', async () => {
const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER });
await assert.rejects(
() => client.putClientState([{ namespace: 'a', key: 'b', value: 'c', updatedAt: 1 }]),
/Not initialised/
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

为了配合新增的参数校验,建议在测试文件中补充对应的单元测试,确保非法参数能够正确触发 TypeError

test('putClientState() before init() throws Not initialised', async () => {
  const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER });
  await assert.rejects(
    () => client.putClientState([{ namespace: 'a', key: 'b', value: 'c', updatedAt: 1 }]),
    /Not initialised/
  );
});

test('putClientState() and getClientState() validate arguments', async () => {
  const client = await makeInitializedClient();
  await assert.rejects(
    () => client.putClientState(),
    /entries must be a non-empty array/
  );
  await assert.rejects(
    () => client.putClientState([]),
    /entries must be a non-empty array/
  );
  await assert.rejects(
    () => client.getClientState(''),
    /namespace must be a non-empty string/
  );
});

putClientState 校验 entries 非空数组、getClientState 校验 namespace
非空字符串,非法入参直接 TypeError,不再白跑一次加密去吃服务端 400。

Claude-Session: https://claude.ai/code/session_01W5FWFQSHixnqgLgVsLpMDp
@Tosd0

Tosd0 commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

Review 三条建议已在 72a18bf 全部采纳:putClientState 前置校验 entries 非空数组、getClientState 前置校验 namespace 非空字符串(均 TypeError,拦在加密与网络请求之前),并补了对应测试(含「校验期不触网」断言)。client 全套测试 75/75 通过。

@Tosd0
Tosd0 merged commit f979e9f into main Jul 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant