@agentunion/fastaun 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/_packed_docs/CHANGELOG.md +32 -0
- package/_packed_docs/INDEX.md +16 -10
- package/_packed_docs/KITE_DOCS_GUIDE.md +4 -2
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +202 -75
- package/_packed_docs/protocol/README.md +3 -2
- package/_packed_docs/protocol/aun-docs-guide.md +3 -2
- package/_packed_docs/protocol/index.md +6 -5
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +20 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +289 -24
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +30 -6
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +28 -12
- package/_packed_docs/sdk/09-group-rpc-manual.md +102 -28
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
- package/_packed_docs/sdk/INDEX.md +17 -17
- package/dist/client/delivery.d.ts +4 -0
- package/dist/client/delivery.js +173 -8
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/v2-e2ee.js +2 -2
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +23 -1
- package/dist/client.js +37 -2
- package/dist/client.js.map +1 -1
- package/dist/facades.d.ts +6 -0
- package/dist/facades.js +206 -12
- package/dist/facades.js.map +1 -1
- package/dist/group-index.d.ts +109 -0
- package/dist/group-index.js +308 -0
- package/dist/group-index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +3 -1
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
| [group.set_settings](#groupset_settings) | 统一设置群参数(含公告、规则、入群要求、dispatch_mode 等) |
|
|
104
104
|
| [group.get_settings](#groupget_settings) | 统一读取群参数 |
|
|
105
105
|
|
|
106
|
-
**便利方法**:SDK 提供向后兼容的便利方法(`getAnnouncement`/`updateAnnouncement`/`getRules`/`updateRules`/`getJoinRequirements`/`updateJoinRequirements
|
|
106
|
+
**便利方法**:SDK 提供向后兼容的便利方法(`getAnnouncement`/`updateAnnouncement`/`getRules`/`updateRules`/`getJoinRequirements`/`updateJoinRequirements`)。读取方法优先返回 SDK 本地缓存,本地没有对应值时才调用 `get_settings` 初始化;即使观察到远端 etag 不一致也不会自动 pull 远端。indexed 写入方法内部调用 `updateGroupIndex` 生成签名 `group.index` 并通过 `set_settings` CAS 提交。
|
|
107
107
|
|
|
108
108
|
### 群文件系统
|
|
109
109
|
|
|
@@ -951,7 +951,9 @@ aun-group-aid-renew-v1|{group_id}|{group_aid}|{sha256(old_public_key)}|{sha256(n
|
|
|
951
951
|
|
|
952
952
|
### group.set_settings
|
|
953
953
|
|
|
954
|
-
统一写入群参数。需要 admin 及以上权限。`dispatch_mode` 是群消息的应用层分发模式标签,会随 `group.send` 生成的消息持久化,并由 SDK 在解密后注入到消息顶层和 `payload.dispatch_mode`。
|
|
954
|
+
统一写入群参数。需要 admin 及以上权限。`dispatch_mode` 是群消息的应用层分发模式标签,会随 `group.send` 生成的消息持久化,并由 SDK 在解密后注入到消息顶层和 `payload.dispatch_mode`。
|
|
955
|
+
|
|
956
|
+
`group.index` 是保留设置 key,用于保存 owner/admin SDK 生成并签名的群索引。更新 indexed settings 时必须同包提交新的签名 `group.index`,并通过 `expected_index_etag` 做 CAS。
|
|
955
957
|
|
|
956
958
|
`dispatch_mode` 不是 `group.send` 的单次入参;要修改后续消息的模式,请通过 `group.set_settings` 更新群设置。
|
|
957
959
|
|
|
@@ -960,10 +962,12 @@ aun-group-aid-renew-v1|{group_id}|{group_aid}|{sha256(old_public_key)}|{sha256(n
|
|
|
960
962
|
| 参数 | 类型 | 必填 | 说明 |
|
|
961
963
|
|------|------|------|------|
|
|
962
964
|
| `group_id` | string | 是 | 群组标识;兼容字段,值语义为目标态 `group_aid` |
|
|
963
|
-
| `settings` | object | 是 | 要写入的设置键值 |
|
|
964
|
-
| `
|
|
965
|
-
| `settings["
|
|
966
|
-
| `settings["
|
|
965
|
+
| `settings` | object | 是 | 要写入的设置键值 |
|
|
966
|
+
| `expected_index_etag` | string | 写 `group.index` 时必填 | CAS 期望旧 etag;空字符串表示只允许创建首个 `group.index` |
|
|
967
|
+
| `settings["dispatch_mode"]` | string | 否 | `"broadcast"` / `"mention"`,默认 `"broadcast"` |
|
|
968
|
+
| `settings["rules.content"]` | string | 否 | 群规则正文 |
|
|
969
|
+
| `settings["announcement.content"]` | string | 否 | 群公告正文 |
|
|
970
|
+
| `settings["group.index"]` | object | 更新 indexed settings 时必填 | 签名 group index,当前结构至少包含 `body` |
|
|
967
971
|
|
|
968
972
|
**预定义群级参数**:
|
|
969
973
|
|
|
@@ -979,11 +983,28 @@ aun-group-aid-renew-v1|{group_id}|{group_aid}|{sha256(old_public_key)}|{sha256(n
|
|
|
979
983
|
| `join.mode` | string | 按 `visibility` 推导:`public -> open`,`private -> approval` | 入群模式:`"open"` / `"approval"` / `"invite_only"` / `"closed"` |
|
|
980
984
|
| `join.question` | string | `""` | 入群问题 |
|
|
981
985
|
| `join.auto_approve_patterns` | array | `[]` | 自动批准 AID 匹配规则 |
|
|
982
|
-
| `join.max_pending` | integer | `100` | 最大待审批入群申请数 |
|
|
983
|
-
| `dispatch_mode` | string | `"broadcast"` | 群消息分发标签:`"broadcast"` / `"mention"`;未显式设置时 `get_settings` 仍返回默认值 |
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
986
|
+
| `join.max_pending` | integer | `100` | 最大待审批入群申请数 |
|
|
987
|
+
| `dispatch_mode` | string | `"broadcast"` | 群消息分发标签:`"broadcast"` / `"mention"`;未显式设置时 `get_settings` 仍返回默认值 |
|
|
988
|
+
| `group.index` | object | — | 保留 key;签名 JSONL 群索引,由 SDK 生成,服务端只校验、CAS 保存和返回 |
|
|
989
|
+
|
|
990
|
+
**indexed settings**:
|
|
991
|
+
|
|
992
|
+
| key | 说明 |
|
|
993
|
+
|-----|------|
|
|
994
|
+
| `rules.content` / `rules.attachments` | 群规则正文与附件稳定引用 |
|
|
995
|
+
| `announcement.content` / `announcement.attachments` | 群公告正文与附件稳定引用 |
|
|
996
|
+
| `join.mode` / `join.question` / `join.auto_approve_patterns` / `join.max_pending` | 入群要求配置 |
|
|
997
|
+
|
|
998
|
+
写入规则:
|
|
999
|
+
|
|
1000
|
+
- 只更新非 indexed settings 时,继续直接调用 `set_settings`,不需要 `group.index`。
|
|
1001
|
+
- 更新任意 indexed setting 时,必须在同一次 `settings` 中携带签名 `group.index`。
|
|
1002
|
+
- 写入 `group.index` 时必须传 `expected_index_etag`。
|
|
1003
|
+
- 服务端在同一事务内比较当前 `group.index` etag、写 indexed settings、写 `group.index`。
|
|
1004
|
+
- CAS 失败时错误消息包含 `group.index etag conflict`;SDK 的 `updateGroupIndex` 会重新读取当前 index、重建签名并按 `max_attempts` 重试。
|
|
1005
|
+
|
|
1006
|
+
```python
|
|
1007
|
+
await client.call("group.set_settings", {
|
|
987
1008
|
"group_id": "g-abc123.agentid.pub",
|
|
988
1009
|
"settings": {"dispatch_mode": "mention"},
|
|
989
1010
|
})
|
|
@@ -992,33 +1013,86 @@ await client.call("group.set_settings", {
|
|
|
992
1013
|
**响应**:
|
|
993
1014
|
|
|
994
1015
|
```json
|
|
995
|
-
{
|
|
996
|
-
"group_id": "g-abc123.agentid.pub",
|
|
997
|
-
"
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1016
|
+
{
|
|
1017
|
+
"group_id": "g-abc123.agentid.pub",
|
|
1018
|
+
"group_aid": "g-abc123.agentid.pub",
|
|
1019
|
+
"updated_keys": ["dispatch_mode"]
|
|
1020
|
+
}
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
写入 `group.index` 成功时,响应顶层会强制携带 `_meta.group_indexes`:
|
|
1024
|
+
|
|
1025
|
+
```json
|
|
1026
|
+
{
|
|
1027
|
+
"group_id": "g-abc123.agentid.pub",
|
|
1028
|
+
"group_aid": "g-abc123.agentid.pub",
|
|
1029
|
+
"updated_keys": ["announcement.content", "group.index"],
|
|
1030
|
+
"_meta": {
|
|
1031
|
+
"group_indexes": {
|
|
1032
|
+
"g-abc123.agentid.pub": {
|
|
1033
|
+
"etag": "\"sha256:...\"",
|
|
1034
|
+
"last_modified": 1780000000000,
|
|
1035
|
+
"schema": "aun.group.index.v1"
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
`group.index` 的正文格式:
|
|
1043
|
+
|
|
1044
|
+
```jsonl
|
|
1045
|
+
{"type":"index_meta","group_aid":"g-abc123.agentid.pub","etag":"\"sha256:...\"","last_modified":1780000000000,"schema":"aun.group.index.v1","body_hash":"sha256:...","signed_by":"alice.agentid.pub","sig_alg":"ECDSA-P256-SHA256","signature":"base64..."}
|
|
1046
|
+
{"key":"announcement.content","source":"db","etag":"\"sha256:...\"","last_modified":1780000000000}
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
`etag` 和 `body_hash` 都由 index 条目的 canonical JSONL bytes 计算。`signature` 覆盖去掉 `signature` 字段后的 `index_meta` 和正文条目,`signed_by` 必须等于本次 RPC actor AID。服务端不会根据 DB 状态生成 `group.index`。
|
|
1050
|
+
|
|
1051
|
+
### group.get_settings
|
|
1052
|
+
|
|
1053
|
+
统一读取群参数。成员可读;不传 `keys` 时返回核心群资料和 settings 表中的全部设置。未显式设置 `dispatch_mode` 时,服务端仍返回默认值 `"broadcast"`。读取 `keys=["group.index"]` 可从服务端摘取当前签名 `group.index`。
|
|
1004
1054
|
|
|
1005
1055
|
**参数**:
|
|
1006
1056
|
|
|
1007
1057
|
| 参数 | 类型 | 必填 | 说明 |
|
|
1008
1058
|
|------|------|------|------|
|
|
1009
1059
|
| `group_id` | string | 是 | 群组标识;兼容字段,值语义为目标态 `group_aid` |
|
|
1010
|
-
| `keys` | array | 否 | 只读取指定 key,如 `["dispatch_mode", "rules.content"]` |
|
|
1060
|
+
| `keys` | array | 否 | 只读取指定 key,如 `["dispatch_mode", "rules.content"]`;读取 `["group.index"]` 时强制返回 `_meta.group_indexes` |
|
|
1011
1061
|
|
|
1012
1062
|
**响应**:
|
|
1013
1063
|
|
|
1014
1064
|
```json
|
|
1015
|
-
{
|
|
1016
|
-
"group_id": "g-abc123.agentid.pub",
|
|
1017
|
-
"
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1065
|
+
{
|
|
1066
|
+
"group_id": "g-abc123.agentid.pub",
|
|
1067
|
+
"group_aid": "g-abc123.agentid.pub",
|
|
1068
|
+
"settings": [
|
|
1069
|
+
{"key": "dispatch_mode", "value": "broadcast", "updated_at": 1234567890000}
|
|
1070
|
+
]
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
如果服务端已保存 `group.index`,普通 settings 读取可能在顶层返回 `_meta.group_indexes`。该 meta 受服务端注入频率控制;显式读取 `group.index` 时会强制返回:
|
|
1075
|
+
|
|
1076
|
+
```json
|
|
1077
|
+
{
|
|
1078
|
+
"group_id": "g-abc123.agentid.pub",
|
|
1079
|
+
"group_aid": "g-abc123.agentid.pub",
|
|
1080
|
+
"settings": [
|
|
1081
|
+
{"key": "group.index", "value": {"body": "..."}, "updated_by": "alice.agentid.pub", "updated_at": 1780000000000}
|
|
1082
|
+
],
|
|
1083
|
+
"_meta": {
|
|
1084
|
+
"group_indexes": {
|
|
1085
|
+
"g-abc123.agentid.pub": {
|
|
1086
|
+
"etag": "\"sha256:...\"",
|
|
1087
|
+
"last_modified": 1780000000000,
|
|
1088
|
+
"schema": "aun.group.index.v1"
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
SDK 观察到 `_meta.group_indexes` 只记录远端 etag,不会自动覆盖本地 index 或业务缓存。etag 不一致只表示本地与观察到的远端版本不同,方向由应用层决定:`checkGroupIndex` 用于检查是否不同步,`getGroupIndex` 用于显式 pull 远端 manifest 并同步本地缓存,`updateGroupIndex` 用于显式 CAS push 本地 indexed settings。
|
|
1022
1096
|
|
|
1023
1097
|
## 消息
|
|
1024
1098
|
|
|
@@ -11,7 +11,7 @@ AUN SDK 文档位于 `docs/sdk/`,索引文件 `docs/sdk/INDEX.md` 分三层:
|
|
|
11
11
|
- `AUNClient`:认证、连接、状态机、事件和 RPC。
|
|
12
12
|
- `ServiceProxyClient`:Python SDK 的 Service Proxy provider 侧客户端,负责 Gateway 控制面注册和 proxy-server 数据面隧道注册。
|
|
13
13
|
|
|
14
|
-
业务 RPC 的底层参数见 `09-*-rpc-manual.md`,可通过 `client.call(method, params)` 调用;Storage、Collab、Group FS、Service Proxy 等具备高层门面的能力以对应专项文档为准。`message.send`、`message.thought.put`、`group.send`、`group.thought.put` 的业务 payload 见 `09-payload-reference.md`。群组标识以 `group_aid` 为主,`group_id`
|
|
14
|
+
业务 RPC 的底层参数见 `09-*-rpc-manual.md`,可通过 `client.call(method, params)` 调用;Storage、Collab、Group FS、Service Proxy 等具备高层门面的能力以对应专项文档为准。`message.send`、`message.thought.put`、`group.send`、`group.thought.put` 的业务 payload 见 `09-payload-reference.md`。群组标识以 `group_aid` 为主,`group_id` 只作为兼容参数名;`group.index` 的签名索引、`_meta.group_indexes` 观察和 `expected_index_etag` CAS 详见 `09-group-rpc-manual.md`。在线轻量通知和跨域 federation notify 方案见 `Notify通知方案.md`。
|
|
15
15
|
|
|
16
16
|
Storage 的 SDK VFS、控制面/数据面分离、direct backend 上传下载、类 Linux 权限、mount/symlink 和服务端分层见 `AUN Storage架构设计.md`。
|
|
17
17
|
Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI、`cp/mv` 语义、群自有区角色 ACL 授权/撤销/查询与 `group_aid` 签名、`parents` 语义、JS string 差异和 memberdata 服务端映射见 `09-group-rpc-manual.md`。Collab 的 `gc`、`reflog`、`revert`、`clone` 及版本化文档/标签语义见 `09-collab-rpc-manual.md`。
|
|
@@ -31,7 +31,8 @@ Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI
|
|
|
31
31
|
- E2EE:默认加密、ProtectedHeaders、P2P / Group V2
|
|
32
32
|
- RPC 与事件:`client.call()`、`client.on()`、`client.notify()`、RPC 手册
|
|
33
33
|
- Storage:SDK VFS、direct backend 数据面、`storage.*` low-level RPC、类 Linux 权限、mount/symlink、Storage Service 分层
|
|
34
|
-
-
|
|
34
|
+
- Group RPC:`group_aid` / `group_id` 兼容、`group.index` 签名索引、`_meta.group_indexes`、`check/get/updateGroupIndex`、`expected_index_etag` CAS(见 `09-group-rpc-manual.md`、`06-API手册.md`)
|
|
35
|
+
- 群文件系统:`group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` 上传下载、群自有区角色 ACL 与 `group_aid` 签名、`parents`、JS string 差异和 memberdata 服务端映射(见 `09-group-rpc-manual.md`)
|
|
35
36
|
- 协作(collab):`collab.*` 版本化文档、乐观锁 CAS commit、三方合并、标签、GC、reflog、revert(见 `09-collab-rpc-manual.md`)
|
|
36
37
|
- Service Proxy:`ServiceProxyClient`、`proxy.register_services`、proxy-server `register_services`
|
|
37
38
|
- agent.md:`AIDStore.upload_agent_md()`、`AIDStore.download_agent_md()`、`AIDStore.check_agent_md()`、Gateway `_meta.agent_md_etags` 被动观察、`requester` / `peer` / `group` 角色
|
|
@@ -47,7 +48,7 @@ Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI
|
|
|
47
48
|
|------|------|------|
|
|
48
49
|
| 01 | [快速开始](01-快速开始.md) | 安装、三主体模型、最小示例 |
|
|
49
50
|
| 02 | [WebSocket协议](02-WebSocket协议.md) | 握手流程、消息格式、裸 WebSocket |
|
|
50
|
-
| 03 | [核心概念](03-核心概念.md) | AID、状态机、认证、E2EE |
|
|
51
|
+
| 03 | [核心概念](03-核心概念.md) | AID、状态机、认证、E2EE、Group Index 观察模型 |
|
|
51
52
|
| 04 | [连接与认证](04-连接与认证.md) | AIDStore、连接、网关发现、事件、agent.md 自动观察 |
|
|
52
53
|
| 05 | [E2EE加密通信](05-E2EE加密通信.md) | E2EE V2、ProtectedHeaders、agent.md 版本提示、recipient wrap |
|
|
53
54
|
| 06 | [API手册](06-API手册.md) | AIDStore / AID / AUNClient / agent.md / ServiceProxyClient / 事件 / RPC |
|
|
@@ -70,7 +71,8 @@ Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI
|
|
|
70
71
|
| 查方法签名 | [06-API手册](06-API手册.md) |
|
|
71
72
|
| 查消息或群组 RPC | 对应 `09-*-rpc-manual.md` |
|
|
72
73
|
| 查群组标识 `group_aid` / `group_id` 兼容规范 | [09-group-rpc-manual.md](09-group-rpc-manual.md) |
|
|
73
|
-
|
|
|
74
|
+
| 查 group.index 签名索引、版本观察、不同步判断、CAS 更新和 facade | [03-核心概念.md](03-核心概念.md)、[06-API手册.md](06-API手册.md)、[09-group-rpc-manual.md](09-group-rpc-manual.md)、[08-最佳实践.md](08-最佳实践.md) |
|
|
75
|
+
| 查群文件系统 RPC / SDK / CLI 入口、群自有区角色 ACL 授权/撤销/查询、`parents` 和 JS string 差异 | [09-group-rpc-manual.md](09-group-rpc-manual.md)、[06-API手册](06-API手册.md) |
|
|
74
76
|
| 查 Storage 架构、SDK VFS、权限模型、mount/symlink 和上传下载链路 | [AUN Storage架构设计.md](<AUN Storage架构设计.md>) |
|
|
75
77
|
| 查 Storage RPC 参数 | [09-storage-rpc-manual.md](09-storage-rpc-manual.md) |
|
|
76
78
|
| 查协作(版本化文档/乐观锁/标签/GC/reflog/revert)RPC | [09-collab-rpc-manual.md](09-collab-rpc-manual.md) |
|
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
|------|------|
|
|
11
11
|
| [01-快速开始](01-快速开始.md) | 安装 · 三主体模型 · 最小示例 · 多语言构造 |
|
|
12
12
|
| [02-WebSocket协议](02-WebSocket协议.md) | 握手流程 · JSON-RPC 消息格式 · 裸 WebSocket 示例 |
|
|
13
|
-
| [03-核心概念](03-核心概念.md) | AID · AIDStore · AUNClient · 九态状态机 · E2EE |
|
|
13
|
+
| [03-核心概念](03-核心概念.md) | AID · AIDStore · AUNClient · 九态状态机 · E2EE · Group Index 观察模型 |
|
|
14
14
|
| [04-连接与认证](04-连接与认证.md) | 注册加载 · 认证连接 · 网关发现 · 事件 · agent.md 自动观察 |
|
|
15
15
|
| [05-E2EE加密通信](05-E2EE加密通信.md) | E2EE V2 消息 · ProtectedHeaders · agent.md 版本提示 · recipient wrap |
|
|
16
16
|
| [E2EE_V2消息通信时序图](E2EE_V2消息通信时序图.md) | V2 P2P/GROUP 明文与加密主链路 |
|
|
17
17
|
| [06-API手册](06-API手册.md) | AIDStore · AID · AUNClient · 事件 · agent.md · E2EE 高级 API · RPC 索引 |
|
|
18
18
|
| [07-错误处理](07-错误处理.md) | Result · 错误类层级 · 错误码 · 重试 |
|
|
19
|
-
| [08-最佳实践](08-最佳实践.md) | 幂等连接 · 多 AID · 资源清理 · 测试数据保护 |
|
|
19
|
+
| [08-最佳实践](08-最佳实践.md) | 幂等连接 · 多 AID · 资源清理 · group.index 更新流程 · 测试数据保护 |
|
|
20
20
|
| [09-payload-reference](09-payload-reference.md) | message / group / thought payload 格式 |
|
|
21
21
|
| [09-message-rpc-manual](09-message-rpc-manual.md) | P2P 消息 RPC |
|
|
22
|
-
| [09-group-rpc-manual](09-group-rpc-manual.md) | 群组 RPC(含 `group_aid` / `group_id` 兼容规范、`group.fs.*` 群文件系统 RPC、群自有区角色 ACL 授权/撤销/查询和 `parents` 语义) |
|
|
22
|
+
| [09-group-rpc-manual](09-group-rpc-manual.md) | 群组 RPC(含 `group_aid` / `group_id` 兼容规范、`group.index` 签名索引与 CAS、`group.fs.*` 群文件系统 RPC、群自有区角色 ACL 授权/撤销/查询和 `parents` 语义) |
|
|
23
23
|
| [09-storage-rpc-manual](09-storage-rpc-manual.md) | 存储 RPC(对象 + POSIX VFS / 卷 / 软链 / ACL / token) |
|
|
24
24
|
| [09-collab-rpc-manual](09-collab-rpc-manual.md) | 协作 RPC(版本化文档 / 乐观锁 / 三方合并 / 标签 / GC / reflog / revert) |
|
|
25
25
|
| [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md) | `client.group.fs.*` / `aun group fs` / `group.fs.*` 群文件系统设计,含群自有区角色 ACL、`parents` 和 JS string 语义 |
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
- `client.call()` / `client.on()` → [04-连接与认证](04-连接与认证.md)、[06-API手册](06-API手册.md)
|
|
65
65
|
- `client.notify()` 在线轻量通知、跨域 federation、在线/离线边界 → [Notify通知方案](Notify通知方案.md)
|
|
66
66
|
- Message RPC → [09-message-rpc-manual](09-message-rpc-manual.md)
|
|
67
|
-
- Group RPC、`group_aid` / `group_id`
|
|
67
|
+
- Group RPC、`group_aid` / `group_id` 兼容规范、`group.index` 签名索引、`_meta.group_indexes` 观察、`expected_index_etag` CAS、群文件系统 `group.fs.*` 控制面、群自有区角色 ACL 授权/撤销/查询和 `parents` 语义 → [09-group-rpc-manual](09-group-rpc-manual.md)
|
|
68
68
|
- 下一版群文件系统 `group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` POSIX 化语义和 JS string 差异 → [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md)、[分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md)
|
|
69
69
|
- Storage 架构、SDK VFS、控制面/数据面分离、类 Linux 权限和 mount/symlink → [AUN Storage架构设计](<AUN Storage架构设计.md>)
|
|
70
70
|
- Storage RPC(对象 + `storage.fs.*` / `storage.volume.*` / `storage.fs.touch` / 软链 / ACL / token)→ [09-storage-rpc-manual](09-storage-rpc-manual.md)
|
|
@@ -93,9 +93,9 @@
|
|
|
93
93
|
|
|
94
94
|
说明 Gateway WebSocket 握手、JSON-RPC 请求/响应/通知格式,以及裸 WebSocket 客户端如何借助 SDK 完成 token 获取。
|
|
95
95
|
|
|
96
|
-
### 03-核心概念
|
|
97
|
-
|
|
98
|
-
解释 AID 身份、三主体职责、device_id / slot_id 隔离键、九态状态机、认证挑战-响应、默认 E2EE
|
|
96
|
+
### 03-核心概念
|
|
97
|
+
|
|
98
|
+
解释 AID 身份、三主体职责、device_id / slot_id 隔离键、九态状态机、认证挑战-响应、默认 E2EE 行为、RPC/事件模型,以及 `group.index` 的 `_meta.group_indexes` 观察、不同步标记和应用层 pull/push 边界。
|
|
99
99
|
|
|
100
100
|
### 04-连接与认证
|
|
101
101
|
|
|
@@ -105,21 +105,21 @@
|
|
|
105
105
|
|
|
106
106
|
覆盖默认加密发送、接收自动解密、E2EE V2 多设备 wrap、ProtectedHeaders、agent.md 版本提示与 E2EE 安全边界、`sender_signature` / AAD / recipient proof、群 E2EE 和高级存储扩展边界。
|
|
107
107
|
|
|
108
|
-
### 06-API手册
|
|
109
|
-
|
|
110
|
-
列出 AIDStore、AID、AUNClient、事件、agent.md、ServiceProxyClient、E2EE 高级 API 和 RPC 手册索引,包含 Python / TS / JS / Go 的主要命名差异。
|
|
111
|
-
|
|
112
|
-
### 07-错误处理
|
|
113
|
-
|
|
114
|
-
说明 Result 字典、AUNError 异常层级、错误码映射、重试策略和常见错误场景。
|
|
108
|
+
### 06-API手册
|
|
109
|
+
|
|
110
|
+
列出 AIDStore、AID、AUNClient、事件、agent.md、ServiceProxyClient、E2EE 高级 API、Group Index facade 和 RPC 手册索引,包含 Python / TS / JS / Go 的主要命名差异。
|
|
115
111
|
|
|
116
|
-
###
|
|
112
|
+
### 07-错误处理
|
|
113
|
+
|
|
114
|
+
说明 Result 字典、AUNError 异常层级、错误码映射、重试策略、`group.index etag conflict` CAS 冲突处理和常见错误场景。
|
|
117
115
|
|
|
118
|
-
|
|
116
|
+
### 08-最佳实践
|
|
117
|
+
|
|
118
|
+
给出幂等加载身份、连接、关闭、多 AID 管理、protected_headers、Flow Control、Group Index 更新流程和测试数据保护建议。
|
|
119
119
|
|
|
120
120
|
### 09-*-rpc-manual
|
|
121
121
|
|
|
122
|
-
各业务服务的 RPC 参数、响应和错误语义。Storage、Collab、Group FS、Service Proxy 等明确提供高层门面的能力应优先使用门面;需要精确控制底层参数时再通过 `client.call()` 调用业务 RPC。
|
|
122
|
+
各业务服务的 RPC 参数、响应和错误语义。Group RPC 覆盖 `group.index` 签名索引、`expected_index_etag` CAS 和 `_meta.group_indexes` 观察。Storage、Collab、Group FS、Service Proxy 等明确提供高层门面的能力应优先使用门面;需要精确控制底层参数时再通过 `client.call()` 调用业务 RPC。
|
|
123
123
|
|
|
124
124
|
### AUN Storage架构设计
|
|
125
125
|
|
|
@@ -27,6 +27,8 @@ export declare class MessageDeliveryEngine {
|
|
|
27
27
|
event: string;
|
|
28
28
|
payload: EventPayload;
|
|
29
29
|
};
|
|
30
|
+
messageRecallDedupKey(payload: JsonObject): string;
|
|
31
|
+
publishMessageRecallTombstone(seq: unknown, message: EventPayload): Promise<boolean>;
|
|
30
32
|
recallEventFromGroupMessage(message: EventPayload): JsonObject | null;
|
|
31
33
|
groupRecallDedupKey(groupId: string, payload: JsonObject): string;
|
|
32
34
|
publishGroupRecallTombstone(groupId: string, seq: unknown, message: EventPayload): Promise<boolean>;
|
|
@@ -40,7 +42,9 @@ export declare class MessageDeliveryEngine {
|
|
|
40
42
|
}): void;
|
|
41
43
|
logAppMessagePublish(event: string, payload: unknown, source: string): void;
|
|
42
44
|
messageTargetsCurrentInstance(message: EventPayload): boolean;
|
|
45
|
+
runPushProcessSerialized(ns: string, operation: () => Promise<void>): Promise<void>;
|
|
43
46
|
onRawMessageReceived(data: EventPayload): Promise<void>;
|
|
47
|
+
onRawMessageRecalled(data: EventPayload): Promise<void>;
|
|
44
48
|
processAndPublishMessage(data: EventPayload): Promise<void>;
|
|
45
49
|
onRawGroupMessageCreated(data: EventPayload): Promise<void>;
|
|
46
50
|
processAndPublishGroupMessage(data: EventPayload): Promise<void>;
|
package/dist/client/delivery.js
CHANGED
|
@@ -3,6 +3,7 @@ import { normalizeGroupId } from '../group-id.js';
|
|
|
3
3
|
import { ConnectionState, isJsonObject } from '../types.js';
|
|
4
4
|
const PUSHED_SEQS_LIMIT = 50_000;
|
|
5
5
|
const PENDING_ORDERED_LIMIT = 50_000;
|
|
6
|
+
const MESSAGE_RECALL_SEEN_LIMIT = 10_000;
|
|
6
7
|
const GROUP_RECALL_SEEN_LIMIT = 10_000;
|
|
7
8
|
const SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
|
|
8
9
|
const APP_MESSAGE_ENVELOPE_KEYS = [
|
|
@@ -11,6 +12,13 @@ const APP_MESSAGE_ENVELOPE_KEYS = [
|
|
|
11
12
|
'timestamp', 'created_at', 'encrypted',
|
|
12
13
|
'context', 'protected_headers', 'headers', 'payload_type',
|
|
13
14
|
];
|
|
15
|
+
const RECALL_PAYLOAD_KEYS = [
|
|
16
|
+
'message_ids', 'target_message_seqs',
|
|
17
|
+
'recalled_message_id', 'target_message_id', 'original_message_id',
|
|
18
|
+
'target_seq', 'original_seq',
|
|
19
|
+
'notice_message_id', 'notice_seq', 'event_seq',
|
|
20
|
+
'sender_aid', 'recalled_by', 'recalled_at', 'reason',
|
|
21
|
+
];
|
|
14
22
|
const APP_SEND_ENVELOPE_METHODS = new Set([
|
|
15
23
|
'message.send',
|
|
16
24
|
'group.send',
|
|
@@ -79,6 +87,10 @@ export class MessageDeliveryEngine {
|
|
|
79
87
|
await this.publishOrderedGroupChanged(payload, source);
|
|
80
88
|
return;
|
|
81
89
|
}
|
|
90
|
+
if (event === 'message.recalled') {
|
|
91
|
+
await this.publishMessageRecallTombstone(seq, payload);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
82
94
|
if (pullResponse) {
|
|
83
95
|
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
|
|
84
96
|
if (isPromiseLike(published))
|
|
@@ -308,6 +320,11 @@ export class MessageDeliveryEngine {
|
|
|
308
320
|
event[key] = msg[key];
|
|
309
321
|
}
|
|
310
322
|
}
|
|
323
|
+
for (const key of RECALL_PAYLOAD_KEYS) {
|
|
324
|
+
if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
|
|
325
|
+
event[key] = msg[key];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
311
328
|
const rawIds = event.message_ids;
|
|
312
329
|
let messageIds = Array.isArray(rawIds)
|
|
313
330
|
? rawIds.map((item) => String(item ?? '').trim()).filter(Boolean)
|
|
@@ -349,6 +366,57 @@ export class MessageDeliveryEngine {
|
|
|
349
366
|
return { event: 'message.recalled', payload: recall };
|
|
350
367
|
return { event: 'message.received', payload: message };
|
|
351
368
|
}
|
|
369
|
+
messageRecallDedupKey(payload) {
|
|
370
|
+
const ids = payload.message_ids;
|
|
371
|
+
const idPart = Array.isArray(ids)
|
|
372
|
+
? ids.map((i) => String(i ?? '').trim()).filter(Boolean).sort().join(',')
|
|
373
|
+
: String(ids ?? '').trim();
|
|
374
|
+
if (idPart)
|
|
375
|
+
return `p2p|id:${idPart}`;
|
|
376
|
+
for (const key of ['recalled_message_id', 'target_message_id', 'original_message_id']) {
|
|
377
|
+
const value = String(payload[key] ?? '').trim();
|
|
378
|
+
if (value)
|
|
379
|
+
return `p2p|id:${value}`;
|
|
380
|
+
}
|
|
381
|
+
const rawSeqs = payload.target_message_seqs;
|
|
382
|
+
const seqPart = Array.isArray(rawSeqs)
|
|
383
|
+
? rawSeqs.map((seq) => String(seq ?? '').trim()).filter(Boolean).sort().join(',')
|
|
384
|
+
: String(rawSeqs ?? payload.target_seq ?? payload.original_seq ?? '').trim();
|
|
385
|
+
if (seqPart) {
|
|
386
|
+
const fromAid = String(payload.from ?? payload.from_aid ?? payload.sender_aid ?? '').trim();
|
|
387
|
+
const toAid = String(payload.to ?? payload.to_aid ?? '').trim();
|
|
388
|
+
return `p2p|from:${fromAid}|to:${toAid}|seq:${seqPart}`;
|
|
389
|
+
}
|
|
390
|
+
const tombstoneId = String(payload.tombstone_message_id ?? payload.message_id ?? '').trim();
|
|
391
|
+
if (tombstoneId)
|
|
392
|
+
return `p2p|tombstone:${tombstoneId}`;
|
|
393
|
+
return `p2p|unknown:${Date.now()}:${Math.random()}`;
|
|
394
|
+
}
|
|
395
|
+
async publishMessageRecallTombstone(seq, message) {
|
|
396
|
+
const client = this.runtime.client;
|
|
397
|
+
const eventPayload = this.recallEventFromMessage(message);
|
|
398
|
+
if (!eventPayload)
|
|
399
|
+
return false;
|
|
400
|
+
const dedupKey = this.messageRecallDedupKey(eventPayload);
|
|
401
|
+
let seen = client._messageRecallSeen;
|
|
402
|
+
if (!seen) {
|
|
403
|
+
seen = new Map();
|
|
404
|
+
client._messageRecallSeen = seen;
|
|
405
|
+
}
|
|
406
|
+
if (seen.has(dedupKey)) {
|
|
407
|
+
client._clientLog.debug(`message.recalled dedup suppressed: seq=${String(seq)} key=${dedupKey}`);
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
seen.set(dedupKey, Date.now());
|
|
411
|
+
if (seen.size > MESSAGE_RECALL_SEEN_LIMIT) {
|
|
412
|
+
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
|
|
413
|
+
for (const [oldKey] of drop)
|
|
414
|
+
seen.delete(oldKey);
|
|
415
|
+
}
|
|
416
|
+
await client._publishAppEvent('message.recalled', eventPayload, 'message-recall');
|
|
417
|
+
client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
352
420
|
recallEventFromGroupMessage(message) {
|
|
353
421
|
if (!isJsonObject(message))
|
|
354
422
|
return null;
|
|
@@ -365,6 +433,11 @@ export class MessageDeliveryEngine {
|
|
|
365
433
|
event[key] = msg[key];
|
|
366
434
|
}
|
|
367
435
|
}
|
|
436
|
+
for (const key of RECALL_PAYLOAD_KEYS) {
|
|
437
|
+
if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
|
|
438
|
+
event[key] = msg[key];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
368
441
|
const rawIds = event.message_ids;
|
|
369
442
|
let messageIds = Array.isArray(rawIds)
|
|
370
443
|
? rawIds.map((item) => String(item ?? '').trim()).filter(Boolean)
|
|
@@ -395,9 +468,9 @@ export class MessageDeliveryEngine {
|
|
|
395
468
|
return event;
|
|
396
469
|
}
|
|
397
470
|
groupRecallDedupKey(groupId, payload) {
|
|
398
|
-
// 群撤回去重键:group_id +
|
|
471
|
+
// 群撤回去重键:group_id + 原始消息标识。
|
|
399
472
|
// 一条消息只能被撤回一次(服务端 group_message_recalls uk_recall_msg_id 唯一约束),
|
|
400
|
-
// (group_id, sorted message_ids)
|
|
473
|
+
// (group_id, sorted message_ids) 已能唯一标识一次撤回;缺 message_ids 时按原消息 id/seq 兜底。
|
|
401
474
|
// 去重键不含 recalled_at:占位/通知 tombstone(pull,事务内时间)与在线 push(事务后重取时间)
|
|
402
475
|
// 三条通道对同一次撤回可能携带不同来源的时间戳,纳入 recalled_at 会使去重失效、回调多次。
|
|
403
476
|
// group_id 归一化一次,使去重键与来源无关(pull 用 normalizeGroupId,push 可能是原始值)。
|
|
@@ -405,15 +478,35 @@ export class MessageDeliveryEngine {
|
|
|
405
478
|
const ids = payload.message_ids;
|
|
406
479
|
const idPart = Array.isArray(ids)
|
|
407
480
|
? ids.map((i) => String(i ?? '').trim()).filter(Boolean).sort().join(',')
|
|
408
|
-
: String(ids ?? '');
|
|
409
|
-
|
|
481
|
+
: String(ids ?? '').trim();
|
|
482
|
+
if (idPart)
|
|
483
|
+
return `${normalizedGroupId}|id:${idPart}`;
|
|
484
|
+
for (const key of ['recalled_message_id', 'target_message_id', 'original_message_id']) {
|
|
485
|
+
const value = String(payload[key] ?? '').trim();
|
|
486
|
+
if (value)
|
|
487
|
+
return `${normalizedGroupId}|id:${value}`;
|
|
488
|
+
}
|
|
489
|
+
const rawSeqs = payload.target_message_seqs;
|
|
490
|
+
const seqPart = Array.isArray(rawSeqs)
|
|
491
|
+
? rawSeqs.map((seq) => String(seq ?? '').trim()).filter(Boolean).sort().join(',')
|
|
492
|
+
: String(rawSeqs ?? payload.original_seq ?? '').trim();
|
|
493
|
+
if (seqPart)
|
|
494
|
+
return `${normalizedGroupId}|seq:${seqPart}`;
|
|
495
|
+
const tombstoneId = String(payload.tombstone_message_id ?? payload.message_id ?? '').trim();
|
|
496
|
+
if (tombstoneId)
|
|
497
|
+
return `${normalizedGroupId}|tombstone:${tombstoneId}`;
|
|
498
|
+
return `${normalizedGroupId}|unknown:${Date.now()}:${Math.random()}`;
|
|
410
499
|
}
|
|
411
500
|
async publishGroupRecallTombstone(groupId, seq, message) {
|
|
412
501
|
const client = this.runtime.client;
|
|
413
502
|
const eventPayload = this.recallEventFromGroupMessage(message);
|
|
414
503
|
if (!eventPayload)
|
|
415
504
|
return false;
|
|
416
|
-
const
|
|
505
|
+
const rawGroupId = String(eventPayload.group_id ?? groupId ?? '').trim();
|
|
506
|
+
const dedupGroupId = normalizeGroupId(rawGroupId) || rawGroupId;
|
|
507
|
+
if (dedupGroupId)
|
|
508
|
+
eventPayload.group_id = dedupGroupId;
|
|
509
|
+
const dedupKey = this.groupRecallDedupKey(dedupGroupId, eventPayload);
|
|
417
510
|
let seen = client._groupRecallSeen;
|
|
418
511
|
if (!seen) {
|
|
419
512
|
seen = new Map();
|
|
@@ -605,6 +698,29 @@ export class MessageDeliveryEngine {
|
|
|
605
698
|
}
|
|
606
699
|
return true;
|
|
607
700
|
}
|
|
701
|
+
async runPushProcessSerialized(ns, operation) {
|
|
702
|
+
const client = this.runtime.client;
|
|
703
|
+
if (!ns) {
|
|
704
|
+
await operation();
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
let queues = client._pushProcessQueues;
|
|
708
|
+
if (!queues) {
|
|
709
|
+
queues = new Map();
|
|
710
|
+
client._pushProcessQueues = queues;
|
|
711
|
+
}
|
|
712
|
+
const previous = queues.get(ns) ?? Promise.resolve();
|
|
713
|
+
const current = previous.catch(() => undefined).then(operation);
|
|
714
|
+
const stored = current.then(() => undefined, () => undefined);
|
|
715
|
+
queues.set(ns, stored);
|
|
716
|
+
try {
|
|
717
|
+
await current;
|
|
718
|
+
}
|
|
719
|
+
finally {
|
|
720
|
+
if (queues.get(ns) === stored)
|
|
721
|
+
queues.delete(ns);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
608
724
|
async onRawMessageReceived(data) {
|
|
609
725
|
const client = this.runtime.client;
|
|
610
726
|
const tStart = Date.now();
|
|
@@ -615,7 +731,10 @@ export class MessageDeliveryEngine {
|
|
|
615
731
|
else {
|
|
616
732
|
client._clientLog.debug('_onRawMessageReceived enter: non-object payload');
|
|
617
733
|
}
|
|
618
|
-
|
|
734
|
+
const ns = isJsonObject(data) && client._aid && data.seq !== undefined
|
|
735
|
+
? `p2p:${client._aid}`
|
|
736
|
+
: '';
|
|
737
|
+
this.runPushProcessSerialized(ns, () => this.processAndPublishMessage(data)).catch((exc) => {
|
|
619
738
|
client._clientLog.warn(`P2P message decrypt failed: ${formatDeliveryError(exc)}`);
|
|
620
739
|
if (isJsonObject(data)) {
|
|
621
740
|
const src = data;
|
|
@@ -633,6 +752,39 @@ export class MessageDeliveryEngine {
|
|
|
633
752
|
});
|
|
634
753
|
client._clientLog.debug(`_onRawMessageReceived exit: elapsed=${Date.now() - tStart}ms (handler dispatched)`);
|
|
635
754
|
}
|
|
755
|
+
async onRawMessageRecalled(data) {
|
|
756
|
+
const client = this.runtime.client;
|
|
757
|
+
if (!isJsonObject(data))
|
|
758
|
+
return;
|
|
759
|
+
const msg = { ...data };
|
|
760
|
+
if (!('type' in msg))
|
|
761
|
+
msg.type = 'message.recalled';
|
|
762
|
+
if (!this.messageTargetsCurrentInstance(msg))
|
|
763
|
+
return;
|
|
764
|
+
const seq = msg.seq;
|
|
765
|
+
if (seq !== undefined && seq !== null && client._aid) {
|
|
766
|
+
const ns = `p2p:${client._aid}`;
|
|
767
|
+
const seqNum = Number(seq);
|
|
768
|
+
if (Number.isFinite(seqNum) && seqNum > 0)
|
|
769
|
+
client._seqTracker.updateMaxSeen(ns, seqNum);
|
|
770
|
+
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
771
|
+
const published = await this.publishOrderedMessage('message.recalled', ns, seq, msg);
|
|
772
|
+
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
773
|
+
if (Number(seq) > contigAfter && !published) {
|
|
774
|
+
this.fillP2pGap().catch((exc) => client._clientLog.warn(`background recall gap fill failed: ${formatDeliveryError(exc)}`));
|
|
775
|
+
}
|
|
776
|
+
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
777
|
+
if (contig > 0) {
|
|
778
|
+
const ackSeq = this.clampAckSeq('message.ack', 'seq', ns, contig);
|
|
779
|
+
client._withBackgroundRpc(() => client._ackV2(ackSeq))
|
|
780
|
+
.catch((e) => { client._clientLog.debug(`P2P recall auto-ack failed: ${formatDeliveryError(e)}`); });
|
|
781
|
+
}
|
|
782
|
+
if (contigAfter !== contigBefore)
|
|
783
|
+
this.persistSeq(ns);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
await this.publishMessageRecallTombstone(seq, msg);
|
|
787
|
+
}
|
|
636
788
|
async processAndPublishMessage(data) {
|
|
637
789
|
const client = this.runtime.client;
|
|
638
790
|
if (!isJsonObject(data)) {
|
|
@@ -692,7 +844,10 @@ export class MessageDeliveryEngine {
|
|
|
692
844
|
else {
|
|
693
845
|
client._clientLog.debug('_onRawGroupMessageCreated enter: non-object payload');
|
|
694
846
|
}
|
|
695
|
-
|
|
847
|
+
const groupNs = isJsonObject(data)
|
|
848
|
+
? String(data.group_id ?? '').trim()
|
|
849
|
+
: '';
|
|
850
|
+
this.runPushProcessSerialized(groupNs ? `group:${groupNs}` : '', () => this.processAndPublishGroupMessage(data)).catch((exc) => {
|
|
696
851
|
client._clientLog.warn(`group message decrypt failed: ${formatDeliveryError(exc)}`);
|
|
697
852
|
if (isJsonObject(data)) {
|
|
698
853
|
const src = data;
|
|
@@ -1720,6 +1875,9 @@ export class MessageDeliveryEngine {
|
|
|
1720
1875
|
const seqNum = Number(seq);
|
|
1721
1876
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
|
|
1722
1877
|
client._clientLog.debug(`publish pulled direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
|
|
1878
|
+
if (event === 'message.recalled') {
|
|
1879
|
+
return this.publishMessageRecallTombstone(seq, payload);
|
|
1880
|
+
}
|
|
1723
1881
|
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, 'pull'));
|
|
1724
1882
|
if (isPromiseLike(published))
|
|
1725
1883
|
await published;
|
|
@@ -1733,15 +1891,22 @@ export class MessageDeliveryEngine {
|
|
|
1733
1891
|
client._pendingOrderedMsgs.delete(ns);
|
|
1734
1892
|
return false;
|
|
1735
1893
|
}
|
|
1894
|
+
await this.drainOrderedMessages(ns, seqNum, true);
|
|
1736
1895
|
queue?.delete(seqNum);
|
|
1737
1896
|
if (queue && queue.size === 0)
|
|
1738
1897
|
client._pendingOrderedMsgs.delete(ns);
|
|
1898
|
+
if (event === 'message.recalled') {
|
|
1899
|
+
const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload);
|
|
1900
|
+
this.markPublishedSeq(ns, seqNum);
|
|
1901
|
+
client._markPulledSeqDelivered(ns, seqNum);
|
|
1902
|
+
client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
1903
|
+
return recallPublished;
|
|
1904
|
+
}
|
|
1739
1905
|
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, 'pull'));
|
|
1740
1906
|
if (isPromiseLike(published))
|
|
1741
1907
|
await published;
|
|
1742
1908
|
this.markPublishedSeq(ns, seqNum);
|
|
1743
1909
|
client._markPulledSeqDelivered(ns, seqNum);
|
|
1744
|
-
await this.drainOrderedMessages(ns, undefined, true);
|
|
1745
1910
|
client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
1746
1911
|
return true;
|
|
1747
1912
|
}
|