feat(amsg): client 新增 client_state 三方法,对接单用户 worker 云端状态镜像#23
Conversation
putClientState 批量 upsert(走既有加密链路,一次请求发完变更)、 getClientState 按 namespace 取回并解密、clearClientState 全量清空。 配了 serverToken 时均带 X-Client-Token。 Claude-Session: https://claude.ai/code/session_01W5FWFQSHixnqgLgVsLpMDp
There was a problem hiding this comment.
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.
| async putClientState(entries) { | ||
| const json = JSON.stringify({ entries }); |
There was a problem hiding this comment.
在进行高开销的加密操作(_encrypt)和网络请求之前,建议在客户端对 entries 参数进行前置校验。如果 entries 未定义、不是数组或为空数组,JSON.stringify({ entries }) 会产生非预期的 JSON(例如 entries 为 undefined 时会变成 "{}"),且会白白消耗加密算力并发送注定失败的请求。
| 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 }); |
| async getClientState(namespace) { | ||
| const res = await fetch( |
There was a problem hiding this comment.
建议在客户端对 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(| 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/ | ||
| ); | ||
| }); |
There was a problem hiding this comment.
为了配合新增的参数校验,建议在测试文件中补充对应的单元测试,确保非法参数能够正确触发 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
|
Review 三条建议已在 72a18bf 全部采纳: |
改了什么
给
@rei-standard/amsg-client补上/client-state端点(amsg-server 2.6.0-next.3 已上线)的配套方法,前端不用再自己拼加密请求:putClientState(entries)init()),服务端按updatedAt最后写赢,重发旧批次无害getClientState(namespace)listMessages)clearClientState()配了
serverToken时三个方法都带X-Client-Token。验证
test/client-state.test.mjs6 个用例:请求形状(URL/method/加密头/token)、密文 body 不含明文 + 解密 roundtrip、加密响应解密、非 success 响应透传、未init()报错tsup构建与 d.ts 生成正常@rei-standard/amsg-clientminorhttps://claude.ai/code/session_01W5FWFQSHixnqgLgVsLpMDp