@agentunion/fastaun-browser 0.2.19 → 0.2.20
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 +26 -0
- package/_packed_docs/CHANGELOG.md +26 -0
- package/_packed_docs/agent.md/SCHEMA.md +173 -0
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +61 -0
- package/_packed_docs/agent.md/examples/human-developer.md +60 -0
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +52 -0
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +43 -0
- package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +205 -0
- package/_packed_docs/protocol/00A-/350/256/276/350/256/241/345/216/237/345/210/231-/344/270/272Agent/350/200/214/347/224/237.md +197 -0
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +549 -0
- package/_packed_docs/protocol/02-/350/257/201/344/271/246/344/270/216/344/277/241/344/273/273/344/275/223/347/263/273.md +810 -0
- package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +262 -0
- package/_packed_docs/protocol/04-Peer-/345/255/220/345/215/217/350/256/256.md +180 -0
- package/_packed_docs/protocol/05-Relay-/345/255/220/345/215/217/350/256/256.md +164 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1135 -0
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +234 -0
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +900 -0
- package/_packed_docs/protocol/08-AUN-E2EE.md +413 -0
- package/_packed_docs/protocol/09-/345/256/211/345/205/250/350/200/203/350/231/221.md +316 -0
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +804 -0
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +271 -0
- package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +329 -0
- package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +141 -0
- package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -0
- package/_packed_docs/protocol/README.md +71 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +118 -0
- package/_packed_docs/protocol/agent.md/examples/codeagent-claudecode.md +61 -0
- package/_packed_docs/protocol/agent.md/examples/human-developer.md +60 -0
- package/_packed_docs/protocol/agent.md/examples/openclaw-lobster.md +52 -0
- package/_packed_docs/protocol/aun-docs-guide.md +49 -0
- package/_packed_docs/protocol/index.md +114 -0
- package/_packed_docs/protocol//350/215/211/346/241/210-agent.md/347/255/276/345/220/215/345/215/217/350/256/256.md +205 -0
- package/_packed_docs/protocol//350/215/211/346/241/210-/346/213/222/347/273/235/344/277/241/345/217/267/345/215/217/350/256/256.md +249 -0
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +337 -0
- package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +80 -0
- package/_packed_docs/protocol//351/231/204/345/275/225C-/347/247/201/351/222/245/347/256/241/347/220/206/344/270/216/350/272/253/344/273/275/346/201/242/345/244/215.md +704 -0
- package/_packed_docs/protocol//351/231/204/345/275/225D-Root_CA_/346/262/273/347/220/206/346/234/272/345/210/266.md +620 -0
- package/_packed_docs/protocol//351/231/204/345/275/225E-Root_CA_/345/207/206/345/205/245/346/265/201/347/250/213.md +605 -0
- package/_packed_docs/protocol//351/231/204/345/275/225F-Issuer_CA_/347/224/263/350/257/267/346/265/201/347/250/213.md +548 -0
- package/_packed_docs/protocol//351/231/204/345/275/225G-AID_/345/255/244/345/204/277/351/242/204/351/230/262/344/270/216/346/225/221/346/217/264/346/234/272/345/210/266.md +513 -0
- package/_packed_docs/protocol//351/231/204/345/275/225H-Identity/346/234/215/345/212/241/345/256/236/347/216/260/346/214/207/345/215/227.md +619 -0
- package/_packed_docs/protocol//351/231/204/345/275/225I-/350/267/250/345/237/237/346/266/210/346/201/257/350/267/257/347/224/261/345/256/236/347/216/260/346/214/207/345/215/227.md +492 -0
- package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +402 -0
- package/_packed_docs/protocol//351/231/204/345/275/225K-Agent_Web/345/217/221/347/216/260/345/215/217/350/256/256.md +130 -0
- package/_packed_docs/protocol//351/231/204/345/275/225L-E2EE/345/256/236/347/216/260/346/214/207/345/215/227.md +267 -0
- package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +367 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +223 -0
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +354 -0
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +172 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +373 -0
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +611 -0
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1152 -0
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +150 -0
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +89 -0
- package/_packed_docs/sdk/09-custody-api-manual.md +445 -0
- package/_packed_docs/sdk/09-group-rpc-manual.md +1895 -0
- package/_packed_docs/sdk/09-message-rpc-manual.md +597 -0
- package/_packed_docs/sdk/09-meta-rpc-manual.md +142 -0
- package/_packed_docs/sdk/09-payload-reference.md +702 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +408 -0
- package/_packed_docs/sdk/09-stream-rpc-manual.md +275 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +72 -0
- package/_packed_docs/sdk/INDEX.md +131 -0
- package/_packed_docs/sdk/README.md +307 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +13 -11
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +38 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +179 -97
- package/dist/client.js.map +1 -1
- package/dist/namespaces/auth.d.ts +1 -0
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +20 -6
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/transport.d.ts +9 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +24 -0
- package/dist/transport.js.map +1 -1
- package/package.json +40 -37
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# 附录 I:跨域消息路由实现指南(非规范性)
|
|
2
|
+
|
|
3
|
+
> **本文档为非规范性内容**:提供跨域消息路由的架构设计、实现建议、性能优化和部署指南,不是协议强制要求。
|
|
4
|
+
|
|
5
|
+
## I.1 架构概述
|
|
6
|
+
|
|
7
|
+
AUN 是一个**开放网络**,不同 Issuer 运营的 Gateway 之间可以互相通信,实现跨 Issuer 的消息传递。
|
|
8
|
+
|
|
9
|
+
**核心特性**:
|
|
10
|
+
- **去中心化**:没有中央服务器,每个 Issuer 独立运营
|
|
11
|
+
- **互联互通**:不同 Issuer 的用户可以互相通信
|
|
12
|
+
- **端到端加密**:消息在客户端加密,Gateway 无法解密
|
|
13
|
+
- **证书验证**:通过 X.509 证书链验证对方身份
|
|
14
|
+
|
|
15
|
+
**类比**:
|
|
16
|
+
- 类似电子邮件(alice@gmail.com 可以发送给 bob@outlook.com)
|
|
17
|
+
- 类似 XMPP/Jabber(去中心化即时通讯协议)
|
|
18
|
+
- 类似 Matrix(去中心化通信协议)
|
|
19
|
+
|
|
20
|
+
### I.1.1 架构设计
|
|
21
|
+
|
|
22
|
+
AUN 的跨域路由采用 **Message 服务直连对端 Gateway** 的架构:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
26
|
+
│ Issuer A (aid.pub) │
|
|
27
|
+
│ │
|
|
28
|
+
│ Alice ──→ Gateway A ──→ Message Service A │
|
|
29
|
+
│ │ │
|
|
30
|
+
│ │ 1. 发现 gateway.example.com │
|
|
31
|
+
│ │ via well-known │
|
|
32
|
+
│ │ │
|
|
33
|
+
│ │ 2. 建立 WebSocket 连接 │
|
|
34
|
+
└──────────────────────────────┼─────────────────────────────────┘
|
|
35
|
+
│
|
|
36
|
+
│ WebSocket + JSON-RPC 2.0
|
|
37
|
+
│ (message.send)
|
|
38
|
+
↓
|
|
39
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ Issuer B (example.com) │
|
|
41
|
+
│ │
|
|
42
|
+
│ Gateway B ──→ Message Service B ──→ Bob│
|
|
43
|
+
│ │
|
|
44
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**职责分工**:
|
|
48
|
+
|
|
49
|
+
| 组件 | 职责 |
|
|
50
|
+
|------|------|
|
|
51
|
+
| **Message Service** | 路由决策:判断目标 AID 是本域还是跨域<br>网关发现:通过 well-known 查询对端 Gateway 地址<br>连接管理:建立、维护、复用到对端 Gateway 的连接<br>消息转发:将消息发送到对端 Gateway |
|
|
52
|
+
| **Gateway** | 本地连接管理:维护客户端 WebSocket 连接<br>消息接收:接收来自其他 Issuer 的 Message Service 的连接<br>本地路由:将跨域消息路由到本地客户端 |
|
|
53
|
+
|
|
54
|
+
**关键设计决策**:
|
|
55
|
+
|
|
56
|
+
1. **Message 服务直连 Gateway**(而非 Gateway 间互连)
|
|
57
|
+
- Message 服务负责路由逻辑,Gateway 专注连接管理
|
|
58
|
+
- Message 服务可以灵活控制跨域策略(重试、超时、限流)
|
|
59
|
+
|
|
60
|
+
2. **使用 WebSocket + JSON-RPC 2.0**(而非 HTTP POST)
|
|
61
|
+
- 与客户端协议一致,简化实现
|
|
62
|
+
- 支持双向通信(未来可扩展)
|
|
63
|
+
|
|
64
|
+
3. **按需连接**(而非全连接)
|
|
65
|
+
- Message 服务不预先建立所有跨域连接
|
|
66
|
+
- 首次跨域消息时建立连接,后续复用
|
|
67
|
+
- 空闲超时后关闭连接
|
|
68
|
+
|
|
69
|
+
## I.2 路由场景
|
|
70
|
+
|
|
71
|
+
### I.2.1 场景 1:本域路由
|
|
72
|
+
|
|
73
|
+
发送者和接收者属于同一 Issuer,Message 服务直接在本地路由。
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Alice (alice.aid.pub)
|
|
77
|
+
│
|
|
78
|
+
│ 1. message.send(to: "bob.aid.pub")
|
|
79
|
+
↓
|
|
80
|
+
Gateway A
|
|
81
|
+
│
|
|
82
|
+
│ 2. 转发到 Message Service A
|
|
83
|
+
↓
|
|
84
|
+
Message Service A
|
|
85
|
+
│
|
|
86
|
+
│ 3. 判断:bob.aid.pub 是本域
|
|
87
|
+
│ 4. 查询 Bob 的连接(可能在本 Gateway 或其他 Gateway)
|
|
88
|
+
│ 5. 路由到 Bob 的 Gateway
|
|
89
|
+
↓
|
|
90
|
+
Bob (bob.aid.pub)
|
|
91
|
+
│
|
|
92
|
+
│ 6. event/message.received
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**特点**:
|
|
96
|
+
- 最快,无需跨域查询
|
|
97
|
+
- Message 服务内部路由
|
|
98
|
+
- 延迟最低(< 50ms)
|
|
99
|
+
|
|
100
|
+
### I.2.2 场景 2:跨域路由
|
|
101
|
+
|
|
102
|
+
发送者和接收者属于不同 Issuer,Message 服务需要连接对端 Gateway。
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Alice (alice.aid.pub)
|
|
106
|
+
│
|
|
107
|
+
│ 1. message.send(to: "bob.example.com")
|
|
108
|
+
↓
|
|
109
|
+
Gateway A (aid.pub)
|
|
110
|
+
│
|
|
111
|
+
│ 2. 转发到 Message Service A
|
|
112
|
+
↓
|
|
113
|
+
Message Service A
|
|
114
|
+
│
|
|
115
|
+
│ 3. 判断:bob.example.com 是跨域
|
|
116
|
+
│ 4. 查询 well-known: https://bob.example.com/.well-known/aun-gateway
|
|
117
|
+
│ → 返回 wss://gateway.example.com/aun
|
|
118
|
+
│
|
|
119
|
+
│ 5. 建立到 gateway.example.com 的 WebSocket 连接(或复用已有连接)
|
|
120
|
+
│ 6. 发送 message.send(to: "bob.example.com", ...)
|
|
121
|
+
│
|
|
122
|
+
├──────────────────────────────────────────────────────────────┐
|
|
123
|
+
│ WebSocket + JSON-RPC 2.0 │
|
|
124
|
+
└──────────────────────────────────────────────────────────────┘
|
|
125
|
+
↓
|
|
126
|
+
Gateway B (example.com)
|
|
127
|
+
│
|
|
128
|
+
│ 7. 转发到 Message Service B
|
|
129
|
+
↓
|
|
130
|
+
Message Service B
|
|
131
|
+
│
|
|
132
|
+
│ 8. 本地路由到 Bob
|
|
133
|
+
↓
|
|
134
|
+
Bob (bob.example.com)
|
|
135
|
+
│
|
|
136
|
+
│ 9. event/message.received
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**特点**:
|
|
140
|
+
- 需要网关发现(well-known 查询)
|
|
141
|
+
- 需要建立跨域连接
|
|
142
|
+
- 延迟较高(100-500ms,取决于网络)
|
|
143
|
+
|
|
144
|
+
## I.3 网关发现机制
|
|
145
|
+
|
|
146
|
+
Message 服务通过 **well-known** 机制发现目标 AID 的 Gateway 地址。
|
|
147
|
+
|
|
148
|
+
### I.3.1 发现流程
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
async def discover_gateway(aid: str) -> str:
|
|
152
|
+
"""发现 AID 的 Gateway 地址"""
|
|
153
|
+
# 1. 检查缓存
|
|
154
|
+
cached = await cache.get(f"gateway:{aid}")
|
|
155
|
+
if cached:
|
|
156
|
+
return cached
|
|
157
|
+
|
|
158
|
+
# 2. 查询 well-known
|
|
159
|
+
url = f"https://{aid}/.well-known/aun-gateway"
|
|
160
|
+
response = await http_client.get(url, timeout=5.0)
|
|
161
|
+
gateway_info = response.json()
|
|
162
|
+
|
|
163
|
+
# 3. 提取 WebSocket 地址
|
|
164
|
+
gateway_url = gateway_info["gateway"] # e.g., "wss://gateway.example.com/aun"
|
|
165
|
+
|
|
166
|
+
# 4. 缓存结果(TTL: 1小时)
|
|
167
|
+
await cache.set(f"gateway:{aid}", gateway_url, ttl=3600)
|
|
168
|
+
|
|
169
|
+
return gateway_url
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### I.3.2 Well-Known 响应格式
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"gateway": "wss://gateway.example.com/aun",
|
|
177
|
+
"issuer": "example.com",
|
|
178
|
+
"version": "1.0"
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### I.3.3 缓存策略
|
|
183
|
+
|
|
184
|
+
**本地缓存(单实例)**:
|
|
185
|
+
```python
|
|
186
|
+
# 使用 TTL 缓存
|
|
187
|
+
cache = TTLCache(maxsize=10000, ttl=3600) # 1小时过期
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**分布式缓存(多实例)**:
|
|
191
|
+
```python
|
|
192
|
+
# 使用 Redis
|
|
193
|
+
await redis.setex(f"gateway:{aid}", 3600, gateway_url)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**缓存更新**:
|
|
197
|
+
- 订阅 `client.online` 事件:客户端上线时更新缓存
|
|
198
|
+
- 订阅 `client.offline` 事件:客户端离线时可选择保留缓存(下次上线可能还在同一 Gateway)
|
|
199
|
+
- 连接失败时:清除缓存,强制重新发现
|
|
200
|
+
|
|
201
|
+
### I.3.4 错误处理
|
|
202
|
+
|
|
203
|
+
| 错误 | 处理策略 |
|
|
204
|
+
|------|---------|
|
|
205
|
+
| well-known 查询超时 | 重试 3 次,间隔 1s/2s/5s |
|
|
206
|
+
| well-known 返回 404 | 该 Issuer 不支持 AUN 协议,返回错误 |
|
|
207
|
+
| Gateway 地址无效 | 记录日志,返回错误 |
|
|
208
|
+
| 缓存失效 | 重新查询 well-known |
|
|
209
|
+
|
|
210
|
+
## I.4 连接管理
|
|
211
|
+
|
|
212
|
+
Message 服务需要管理到多个对端 Gateway 的 WebSocket 连接。
|
|
213
|
+
|
|
214
|
+
### I.4.1 连接池设计
|
|
215
|
+
|
|
216
|
+
**按需建立连接**:
|
|
217
|
+
```python
|
|
218
|
+
class CrossDomainConnectionPool:
|
|
219
|
+
def __init__(self):
|
|
220
|
+
self.connections = {} # {gateway_url: WebSocketConnection}
|
|
221
|
+
self.locks = {} # {gateway_url: asyncio.Lock}
|
|
222
|
+
|
|
223
|
+
async def get_connection(self, gateway_url: str) -> WebSocketConnection:
|
|
224
|
+
"""获取到指定 Gateway 的连接(复用或新建)"""
|
|
225
|
+
# 1. 检查是否已有连接
|
|
226
|
+
if gateway_url in self.connections:
|
|
227
|
+
conn = self.connections[gateway_url]
|
|
228
|
+
if conn.is_alive():
|
|
229
|
+
return conn
|
|
230
|
+
else:
|
|
231
|
+
# 连接已断开,清理
|
|
232
|
+
del self.connections[gateway_url]
|
|
233
|
+
|
|
234
|
+
# 2. 获取锁,避免并发建立多个连接
|
|
235
|
+
if gateway_url not in self.locks:
|
|
236
|
+
self.locks[gateway_url] = asyncio.Lock()
|
|
237
|
+
|
|
238
|
+
async with self.locks[gateway_url]:
|
|
239
|
+
# 再次检查(可能其他协程已建立)
|
|
240
|
+
if gateway_url in self.connections:
|
|
241
|
+
return self.connections[gateway_url]
|
|
242
|
+
|
|
243
|
+
# 3. 建立新连接
|
|
244
|
+
conn = await self._create_connection(gateway_url)
|
|
245
|
+
self.connections[gateway_url] = conn
|
|
246
|
+
return conn
|
|
247
|
+
|
|
248
|
+
async def _create_connection(self, gateway_url: str) -> WebSocketConnection:
|
|
249
|
+
"""建立到 Gateway 的 WebSocket 连接"""
|
|
250
|
+
ws = await websockets.connect(
|
|
251
|
+
gateway_url,
|
|
252
|
+
ssl=ssl_context, # TLS 证书验证
|
|
253
|
+
ping_interval=30,
|
|
254
|
+
ping_timeout=10,
|
|
255
|
+
close_timeout=5,
|
|
256
|
+
)
|
|
257
|
+
return WebSocketConnection(ws, gateway_url)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### I.4.2 连接生命周期
|
|
261
|
+
|
|
262
|
+
**连接状态**:
|
|
263
|
+
- `CONNECTING`:正在建立连接
|
|
264
|
+
- `CONNECTED`:连接已建立,可以发送消息
|
|
265
|
+
- `IDLE`:连接空闲(无消息发送)
|
|
266
|
+
- `CLOSED`:连接已关闭
|
|
267
|
+
|
|
268
|
+
**空闲超时**:
|
|
269
|
+
```python
|
|
270
|
+
# 连接空闲 5 分钟后自动关闭
|
|
271
|
+
IDLE_TIMEOUT = 300 # 秒
|
|
272
|
+
|
|
273
|
+
async def monitor_idle_connections():
|
|
274
|
+
while True:
|
|
275
|
+
await asyncio.sleep(60) # 每分钟检查一次
|
|
276
|
+
now = time.time()
|
|
277
|
+
for gateway_url, conn in list(pool.connections.items()):
|
|
278
|
+
if now - conn.last_activity > IDLE_TIMEOUT:
|
|
279
|
+
await conn.close()
|
|
280
|
+
del pool.connections[gateway_url]
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### I.4.3 重连策略
|
|
284
|
+
|
|
285
|
+
**连接断开时**:
|
|
286
|
+
- 不立即重连(按需重连)
|
|
287
|
+
- 下次发送消息时自动重新建立连接
|
|
288
|
+
- 记录断开原因和时间,用于监控
|
|
289
|
+
|
|
290
|
+
**连接失败时**:
|
|
291
|
+
- 重试 3 次,间隔 1s/2s/5s
|
|
292
|
+
- 3 次失败后放弃,返回错误
|
|
293
|
+
- 清除该 Gateway 的缓存,下次重新发现
|
|
294
|
+
|
|
295
|
+
## I.5 消息路由实现
|
|
296
|
+
|
|
297
|
+
### I.5.1 路由决策
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
async def route_message(from_aid: str, to_aid: str, payload: dict) -> dict:
|
|
301
|
+
"""路由消息到目标 AID"""
|
|
302
|
+
# 1. 提取目标 Issuer
|
|
303
|
+
target_issuer = extract_issuer(to_aid) # e.g., "example.com"
|
|
304
|
+
local_issuer = extract_issuer(from_aid) # e.g., "aid.pub"
|
|
305
|
+
|
|
306
|
+
# 2. 判断是否跨域
|
|
307
|
+
if target_issuer == local_issuer:
|
|
308
|
+
# 本域路由
|
|
309
|
+
return await route_local(to_aid, payload)
|
|
310
|
+
else:
|
|
311
|
+
# 跨域路由
|
|
312
|
+
return await route_cross_domain(to_aid, payload)
|
|
313
|
+
|
|
314
|
+
async def route_cross_domain(to_aid: str, payload: dict) -> dict:
|
|
315
|
+
"""跨域路由"""
|
|
316
|
+
# 1. 发现目标 Gateway
|
|
317
|
+
gateway_url = await discover_gateway(to_aid)
|
|
318
|
+
|
|
319
|
+
# 2. 获取连接
|
|
320
|
+
conn = await connection_pool.get_connection(gateway_url)
|
|
321
|
+
|
|
322
|
+
# 3. 发送消息
|
|
323
|
+
result = await conn.call("message.send", {
|
|
324
|
+
"to": to_aid,
|
|
325
|
+
"type": payload["type"],
|
|
326
|
+
"payload": payload["payload"],
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
return result
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### I.5.2 消息格式
|
|
333
|
+
|
|
334
|
+
跨域消息使用标准的 `message.send` 方法,格式与本域消息一致:
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
{
|
|
338
|
+
"jsonrpc": "2.0",
|
|
339
|
+
"id": 123,
|
|
340
|
+
"method": "message.send",
|
|
341
|
+
"params": {
|
|
342
|
+
"to": "bob.example.com",
|
|
343
|
+
"payload": {
|
|
344
|
+
"type": "text",
|
|
345
|
+
"text": "Hello from alice.aid.pub"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### I.5.3 错误处理
|
|
352
|
+
|
|
353
|
+
| 错误场景 | 错误码 | 处理策略 |
|
|
354
|
+
|---------|--------|---------|
|
|
355
|
+
| 目标 AID 不存在 | `USER_NOT_FOUND` | 返回错误给发送者 |
|
|
356
|
+
| 目标 Gateway 不可达 | `GATEWAY_UNREACHABLE` | 重试 3 次,失败后返回错误 |
|
|
357
|
+
| 连接超时 | `TIMEOUT` | 重试,清除缓存 |
|
|
358
|
+
| 目标用户离线 | `USER_OFFLINE` | 根据策略:存储离线消息或返回错误 |
|
|
359
|
+
|
|
360
|
+
## I.6 安全考虑
|
|
361
|
+
|
|
362
|
+
### I.6.1 TLS 证书验证
|
|
363
|
+
|
|
364
|
+
Message 服务连接对端 Gateway 时必须验证 TLS 证书:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
import ssl
|
|
368
|
+
|
|
369
|
+
ssl_context = ssl.create_default_context()
|
|
370
|
+
ssl_context.check_hostname = True
|
|
371
|
+
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
|
372
|
+
|
|
373
|
+
# 连接时使用
|
|
374
|
+
ws = await websockets.connect(gateway_url, ssl=ssl_context)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### I.6.2 防止滥用
|
|
378
|
+
|
|
379
|
+
**限流策略**:
|
|
380
|
+
- 每个 Issuer 的跨域消息限流(例如:1000 msg/s)
|
|
381
|
+
- 单个 AID 的跨域消息限流(例如:10 msg/s)
|
|
382
|
+
- 使用令牌桶或漏桶算法
|
|
383
|
+
|
|
384
|
+
**黑名单机制**:
|
|
385
|
+
```python
|
|
386
|
+
# 恶意 Issuer 黑名单
|
|
387
|
+
BLACKLIST = {"spam.example.com", "malicious.net"}
|
|
388
|
+
|
|
389
|
+
async def route_cross_domain(to_aid: str, payload: dict):
|
|
390
|
+
target_issuer = extract_issuer(to_aid)
|
|
391
|
+
if target_issuer in BLACKLIST:
|
|
392
|
+
raise PermissionError(f"Issuer {target_issuer} is blacklisted")
|
|
393
|
+
# ...
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### I.6.3 消息验证
|
|
397
|
+
|
|
398
|
+
虽然 Message 服务不解密消息内容(E2EE),但应验证消息格式:
|
|
399
|
+
- 检查必填字段(to、type、payload)
|
|
400
|
+
- 验证 AID 格式
|
|
401
|
+
- 限制消息大小(例如:1MB)
|
|
402
|
+
|
|
403
|
+
## I.7 性能优化
|
|
404
|
+
|
|
405
|
+
### I.7.1 连接复用
|
|
406
|
+
|
|
407
|
+
- 同一 Gateway 的多条消息复用同一 WebSocket 连接
|
|
408
|
+
- 避免频繁建立/关闭连接的开销
|
|
409
|
+
- 使用连接池管理
|
|
410
|
+
|
|
411
|
+
### I.7.2 并发控制
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
# 限制并发连接数
|
|
415
|
+
MAX_CONCURRENT_CONNECTIONS = 100
|
|
416
|
+
semaphore = asyncio.Semaphore(MAX_CONCURRENT_CONNECTIONS)
|
|
417
|
+
|
|
418
|
+
async def send_cross_domain_message(to_aid, payload):
|
|
419
|
+
async with semaphore:
|
|
420
|
+
return await route_cross_domain(to_aid, payload)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### I.7.3 批量发送
|
|
424
|
+
|
|
425
|
+
对于同一目标 Gateway 的多条消息,可以批量发送:
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
async def send_batch(gateway_url: str, messages: list):
|
|
429
|
+
conn = await connection_pool.get_connection(gateway_url)
|
|
430
|
+
# 使用 JSON-RPC 批量请求
|
|
431
|
+
batch_request = [
|
|
432
|
+
{"jsonrpc": "2.0", "id": i, "method": "message.send", "params": msg}
|
|
433
|
+
for i, msg in enumerate(messages)
|
|
434
|
+
]
|
|
435
|
+
return await conn.send_batch(batch_request)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## I.8 监控与运维
|
|
439
|
+
|
|
440
|
+
### I.8.1 关键指标
|
|
441
|
+
|
|
442
|
+
| 指标 | 说明 | 告警阈值 |
|
|
443
|
+
|------|------|---------|
|
|
444
|
+
| `cross_domain_message_total` | 跨域消息总数 | - |
|
|
445
|
+
| `cross_domain_message_success_rate` | 成功率 | < 95% |
|
|
446
|
+
| `cross_domain_message_latency_p99` | P99 延迟 | > 1000ms |
|
|
447
|
+
| `gateway_connection_count` | 活跃连接数 | > 500 |
|
|
448
|
+
| `gateway_discovery_failures` | 发现失败次数 | > 10/min |
|
|
449
|
+
| `connection_failures` | 连接失败次数 | > 5/min |
|
|
450
|
+
|
|
451
|
+
### I.8.2 日志记录
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
logger.info("cross_domain_route", extra={
|
|
455
|
+
"from": from_aid,
|
|
456
|
+
"to": to_aid,
|
|
457
|
+
"target_gateway": gateway_url,
|
|
458
|
+
"latency_ms": latency,
|
|
459
|
+
"success": True,
|
|
460
|
+
})
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### I.8.3 故障排查
|
|
464
|
+
|
|
465
|
+
**常见问题**:
|
|
466
|
+
|
|
467
|
+
1. **well-known 查询失败**
|
|
468
|
+
- 检查目标域名 DNS 解析
|
|
469
|
+
- 检查 HTTPS 证书
|
|
470
|
+
- 检查防火墙规则
|
|
471
|
+
|
|
472
|
+
2. **连接超时**
|
|
473
|
+
- 检查网络连通性
|
|
474
|
+
- 检查目标 Gateway 是否在线
|
|
475
|
+
- 检查 TLS 握手
|
|
476
|
+
|
|
477
|
+
3. **消息发送失败**
|
|
478
|
+
- 检查目标 AID 是否存在
|
|
479
|
+
- 检查目标用户是否在线
|
|
480
|
+
- 检查消息格式
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
**总结**:
|
|
485
|
+
|
|
486
|
+
本实现指南描述了基于 Message 服务直连对端 Gateway 的跨域路由架构。核心要点:
|
|
487
|
+
|
|
488
|
+
1. Message 服务通过 well-known 发现对端 Gateway
|
|
489
|
+
2. 按需建立 WebSocket 连接,复用连接池
|
|
490
|
+
3. 使用标准 message.send 方法,格式与本域一致
|
|
491
|
+
4. 注意 TLS 验证、限流、监控
|
|
492
|
+
|