@agentunion/fastaun 0.4.13 → 0.5.1
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 +547 -393
- package/_packed_docs/CHANGELOG-validators.md +144 -0
- package/_packed_docs/CHANGELOG.md +547 -393
- package/_packed_docs/INDEX.md +159 -84
- package/_packed_docs/KITE_DOCS_GUIDE.md +48 -31
- package/_packed_docs/agent.md/SCHEMA.md +2 -0
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +2 -0
- package/_packed_docs/agent.md/examples/human-developer.md +2 -0
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +2 -0
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +2 -0
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +2 -0
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +266 -686
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
- package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +2 -0
- package/_packed_docs/design/AUNClient/346/213/206/345/210/206/351/207/215/346/236/204/346/211/247/350/241/214/346/226/271/346/241/210.md +2 -0
- package/_packed_docs/design/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +2 -0
- package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +2 -0
- package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +2 -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 +2 -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 +2 -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 +2 -0
- package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +2 -0
- package/_packed_docs/protocol/04-Peer-/345/255/220/345/215/217/350/256/256.md +2 -0
- package/_packed_docs/protocol/05-Relay-/345/255/220/345/215/217/350/256/256.md +2 -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 +2 -0
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +296 -900
- package/_packed_docs/protocol/08-AUN-E2EE.md +2 -0
- package/_packed_docs/protocol/09-/345/256/211/345/205/250/350/200/203/350/231/221.md +2 -0
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +66 -173
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +55 -0
- package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +2 -0
- package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +2 -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 +2 -0
- package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +2 -0
- package/_packed_docs/protocol/16-/347/263/273/347/273/237/347/233/256/345/275/225/344/277/235/346/212/244/346/226/271/346/241/210.md +177 -0
- package/_packed_docs/protocol/README.md +4 -1
- package/_packed_docs/protocol/agent.md/SCHEMA.md +2 -0
- package/_packed_docs/protocol/agent.md/examples/codeagent-claudecode.md +2 -0
- package/_packed_docs/protocol/agent.md/examples/human-developer.md +2 -0
- package/_packed_docs/protocol/agent.md/examples/openclaw-lobster.md +2 -0
- package/_packed_docs/protocol/aun-docs-guide.md +2 -0
- package/_packed_docs/protocol/index.md +10 -3
- 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 +2 -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 +2 -0
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +2 -0
- package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +2 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +2 -0
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +2 -0
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +2 -0
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -252
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +219 -177
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +4 -1
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +2 -0
- package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -0
- package/_packed_docs/sdk/09-custody-api-manual.md +2 -0
- package/_packed_docs/sdk/09-group-rpc-manual.md +301 -634
- package/_packed_docs/sdk/09-message-rpc-manual.md +2 -0
- package/_packed_docs/sdk/09-meta-rpc-manual.md +2 -0
- package/_packed_docs/sdk/09-payload-reference.md +2 -0
- package/_packed_docs/sdk/09-proxy-rpc-manual.md +2 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +984 -274
- package/_packed_docs/sdk/09-stream-rpc-manual.md +2 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +36 -23
- package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +2 -0
- package/_packed_docs/sdk/INDEX.md +47 -30
- package/_packed_docs/sdk/Notify/351/200/232/347/237/245/346/226/271/346/241/210.md +2 -0
- package/_packed_docs/sdk/README.md +17 -15
- package/dist/aid-store.d.ts +25 -0
- package/dist/aid-store.js +80 -0
- package/dist/aid-store.js.map +1 -1
- package/dist/client/delivery.d.ts +11 -0
- package/dist/client/delivery.js +208 -25
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +33 -11
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.js +177 -65
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +6 -1
- package/dist/client/v2-e2ee.js +199 -130
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +37 -1
- package/dist/client.js +491 -44
- package/dist/client.js.map +1 -1
- package/dist/collab/client.d.ts +46 -0
- package/dist/collab/client.js +136 -0
- package/dist/collab/client.js.map +1 -0
- package/dist/collab/errors.d.ts +15 -0
- package/dist/collab/errors.js +60 -0
- package/dist/collab/errors.js.map +1 -0
- package/dist/collab/index.d.ts +3 -0
- package/dist/collab/index.js +4 -0
- package/dist/collab/index.js.map +1 -0
- package/dist/collab/types.d.ts +110 -0
- package/dist/collab/types.js +2 -0
- package/dist/collab/types.js.map +1 -0
- package/dist/crypto.d.ts +2 -0
- package/dist/crypto.js +6 -0
- package/dist/crypto.js.map +1 -1
- package/dist/errors.d.ts +0 -12
- package/dist/errors.js +9 -25
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +94 -0
- package/dist/facades.js +255 -0
- package/dist/facades.js.map +1 -0
- package/dist/group-fs.d.ts +122 -0
- package/dist/group-fs.js +467 -0
- package/dist/group-fs.js.map +1 -0
- package/dist/group-id.d.ts +9 -12
- package/dist/group-id.js +41 -63
- package/dist/group-id.js.map +1 -1
- package/dist/group-resources.d.ts +31 -0
- package/dist/group-resources.js +84 -0
- package/dist/group-resources.js.map +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +1 -0
- package/dist/keystore/aid-db.js +43 -43
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/local-identity-store.d.ts +16 -0
- package/dist/keystore/local-identity-store.js +190 -18
- package/dist/keystore/local-identity-store.js.map +1 -1
- package/dist/keystore/local-token-store.d.ts +0 -13
- package/dist/keystore/local-token-store.js +0 -105
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/register-flow.d.ts +1 -0
- package/dist/register-flow.js +3 -0
- package/dist/register-flow.js.map +1 -1
- package/dist/service-proxy.d.ts +1 -0
- package/dist/service-proxy.js +17 -4
- package/dist/service-proxy.js.map +1 -1
- package/dist/storage/errors.d.ts +27 -0
- package/dist/storage/errors.js +65 -0
- package/dist/storage/errors.js.map +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/lowlevel.d.ts +321 -0
- package/dist/storage/lowlevel.js +475 -0
- package/dist/storage/lowlevel.js.map +1 -0
- package/dist/storage/types.d.ts +57 -0
- package/dist/storage/types.js +116 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/storage/vfs.d.ts +166 -0
- package/dist/storage/vfs.js +524 -0
- package/dist/storage/vfs.js.map +1 -0
- package/dist/tools/cross-sdk-agent.js +708 -1
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +5 -0
- package/dist/transport.js +124 -21
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +38 -0
- package/dist/v2/session/session.d.ts +2 -0
- package/dist/v2/session/session.js +53 -23
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts +1 -1
- package/dist/v2/state/commitment.js +4 -2
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/validators.d.ts +32 -0
- package/dist/validators.js +128 -0
- package/dist/validators.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -16,6 +16,7 @@ import * as https from 'node:https';
|
|
|
16
16
|
import { URL } from 'node:url';
|
|
17
17
|
import { configFromMap, getDeviceId, normalizeSlotId } from './config.js';
|
|
18
18
|
import { CryptoProvider } from './crypto.js';
|
|
19
|
+
import { AIDStore } from './aid-store.js';
|
|
19
20
|
import { GatewayDiscovery } from './discovery.js';
|
|
20
21
|
import { DnsResilientNet } from './net.js';
|
|
21
22
|
import { AUNError, AuthError, ConnectionError, E2EEError, NotFoundError, PermissionError, StateError, TimeoutError, ValidationError, } from './errors.js';
|
|
@@ -23,6 +24,7 @@ import { EventDispatcher } from './events.js';
|
|
|
23
24
|
import { LocalTokenStore } from './keystore/local-token-store.js';
|
|
24
25
|
import { AUNLogger } from './logger.js';
|
|
25
26
|
import { normalizeGroupId } from './group-id.js';
|
|
27
|
+
import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
|
|
26
28
|
import { RPCTransport } from './transport.js';
|
|
27
29
|
import { AuthFlow } from './auth.js';
|
|
28
30
|
import { AgentMdManager } from './agent-md.js';
|
|
@@ -33,6 +35,9 @@ import { IdentityRuntimeManager } from './client/identity.js';
|
|
|
33
35
|
import { LifecycleController } from './client/lifecycle.js';
|
|
34
36
|
import { PeerDirectory } from './client/peers.js';
|
|
35
37
|
import { RpcPipeline } from './client/rpc-pipeline.js';
|
|
38
|
+
import { CollabClient } from './collab/client.js';
|
|
39
|
+
import { GroupFacade, MessageFacade, StreamFacade } from './facades.js';
|
|
40
|
+
import { StorageVFS } from './storage/vfs.js';
|
|
36
41
|
import { V2E2EECoordinator } from './client/v2-e2ee.js';
|
|
37
42
|
import { GroupStateCoordinator } from './client/group-state.js';
|
|
38
43
|
import { decryptMessage, } from './v2/e2ee/index.js';
|
|
@@ -134,26 +139,34 @@ const NON_IDEMPOTENT_TIMEOUT_MS = 35_000;
|
|
|
134
139
|
const NON_IDEMPOTENT_METHODS = new Set([
|
|
135
140
|
'message.send', 'group.send', 'group.create', 'group.invite',
|
|
136
141
|
'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
|
|
137
|
-
'group.
|
|
138
|
-
'group.
|
|
142
|
+
'group.set_settings',
|
|
143
|
+
'group.update_announcement', 'group.update_rules',
|
|
139
144
|
'storage.put_object', 'storage.delete_object', 'storage.get_by_share',
|
|
140
145
|
'storage.create_share_link', 'storage.revoke_share_link',
|
|
141
146
|
'storage.create_upload_session', 'storage.complete_upload',
|
|
142
147
|
'storage.create_folder', 'storage.rename_folder', 'storage.move_folder',
|
|
143
148
|
'storage.delete_folder', 'storage.move_object', 'storage.copy_object',
|
|
144
149
|
'storage.batch_delete', 'storage.set_object_meta', 'storage.append_object',
|
|
150
|
+
'storage.set_acl', 'storage.remove_acl', 'storage.set_visibility',
|
|
151
|
+
'storage.issue_token', 'storage.revoke_token',
|
|
152
|
+
'storage.create_symlink', 'storage.atomic_repoint',
|
|
153
|
+
'storage.rename_symlink', 'storage.delete_symlink',
|
|
154
|
+
'storage.fs.mkdir', 'storage.fs.remove', 'storage.fs.rename', 'storage.fs.copy',
|
|
155
|
+
'storage.fs.mount', 'storage.fs.approve', 'storage.fs.reject', 'storage.fs.unmount',
|
|
156
|
+
'storage.fs.invalidate_membership',
|
|
157
|
+
'storage.volume.create', 'storage.volume.renew', 'storage.volume.expire_due',
|
|
158
|
+
'group.fs.mkdir', 'group.fs.rm', 'group.fs.cp', 'group.fs.mv',
|
|
159
|
+
'group.fs.set_acl', 'group.fs.remove_acl',
|
|
160
|
+
'group.fs.mount', 'group.fs.umount',
|
|
161
|
+
'group.fs.check_upload', 'group.fs.create_upload_session',
|
|
162
|
+
'group.fs.complete_upload', 'group.fs.create_download_ticket',
|
|
145
163
|
'auth.create_aid', 'auth.renew_cert', 'auth.rekey',
|
|
146
164
|
'message.thought.put', 'group.thought.put',
|
|
147
|
-
'group.add_member',
|
|
148
|
-
'
|
|
149
|
-
'
|
|
150
|
-
'
|
|
151
|
-
'
|
|
152
|
-
'group.resources.request_add', 'group.resources.request_mount_object',
|
|
153
|
-
'group.resources.direct_add', 'group.resources.approve_request',
|
|
154
|
-
'group.resources.reject_request', 'group.resources.unmount',
|
|
155
|
-
'group.resources.get_access',
|
|
156
|
-
'group.resources.resolve_access_ticket',
|
|
165
|
+
'group.add_member', 'group.bind_group_aid', 'group.complete_transfer',
|
|
166
|
+
'collab.create', 'collab.commit', 'collab.clone',
|
|
167
|
+
'collab.prune', 'collab.unregister',
|
|
168
|
+
'collab.tag.create', 'collab.tag.restore',
|
|
169
|
+
'collab.tag.rm', 'collab.tag.prune',
|
|
157
170
|
]);
|
|
158
171
|
/** 需要客户端签名的关键方法。运行时逻辑已迁入 RpcPipeline,此处保留给源码审计测试。 */
|
|
159
172
|
const SIGNED_METHODS = new Set([
|
|
@@ -167,29 +180,42 @@ const SIGNED_METHODS = new Set([
|
|
|
167
180
|
'group.v2.propose_state', 'group.v2.confirm_state',
|
|
168
181
|
'group.v2.get_proposal',
|
|
169
182
|
'group.kick', 'group.add_member',
|
|
170
|
-
'group.leave', 'group.remove_member',
|
|
171
|
-
'group.update',
|
|
172
|
-
'group.
|
|
173
|
-
'group.transfer_owner', 'group.
|
|
183
|
+
'group.leave', 'group.remove_member',
|
|
184
|
+
'group.update',
|
|
185
|
+
'group.set_role',
|
|
186
|
+
'group.transfer_owner', 'group.bind_group_aid', 'group.renew_group_aid', 'group.complete_transfer',
|
|
187
|
+
'group.review_join_request',
|
|
174
188
|
'group.batch_review_join_request',
|
|
175
189
|
'group.request_join', 'group.use_invite_code',
|
|
176
190
|
'group.thought.put',
|
|
177
191
|
'message.thought.put',
|
|
178
192
|
'group.set_settings',
|
|
179
|
-
'group.
|
|
180
|
-
'group.
|
|
181
|
-
'group.resources.mount_object', 'group.resources.update',
|
|
182
|
-
'group.resources.delete', 'group.resources.cleanup_by_storage_ref',
|
|
183
|
-
'group.resources.request_add', 'group.resources.request_mount_object',
|
|
184
|
-
'group.resources.direct_add', 'group.resources.approve_request',
|
|
185
|
-
'group.resources.reject_request', 'group.resources.unmount',
|
|
186
|
-
'group.resources.get_access', 'group.resources.resolve_access_ticket',
|
|
193
|
+
'group.update_announcement',
|
|
194
|
+
'group.update_rules',
|
|
187
195
|
'storage.put_object', 'storage.delete_object', 'storage.get_by_share',
|
|
188
196
|
'storage.create_share_link', 'storage.revoke_share_link',
|
|
189
197
|
'storage.create_upload_session', 'storage.complete_upload',
|
|
190
198
|
'storage.create_folder', 'storage.rename_folder', 'storage.move_folder',
|
|
191
199
|
'storage.delete_folder', 'storage.move_object', 'storage.copy_object',
|
|
192
200
|
'storage.batch_delete', 'storage.set_object_meta', 'storage.append_object',
|
|
201
|
+
'storage.set_acl', 'storage.remove_acl', 'storage.set_visibility',
|
|
202
|
+
'storage.check_access',
|
|
203
|
+
'storage.issue_token', 'storage.revoke_token',
|
|
204
|
+
'storage.create_symlink', 'storage.atomic_repoint',
|
|
205
|
+
'storage.rename_symlink', 'storage.delete_symlink',
|
|
206
|
+
'storage.fs.mkdir', 'storage.fs.remove', 'storage.fs.rename', 'storage.fs.copy',
|
|
207
|
+
'storage.fs.mount', 'storage.fs.approve', 'storage.fs.reject', 'storage.fs.unmount',
|
|
208
|
+
'storage.fs.invalidate_membership',
|
|
209
|
+
'storage.volume.create', 'storage.volume.renew', 'storage.volume.expire_due',
|
|
210
|
+
'group.fs.mkdir', 'group.fs.rm', 'group.fs.cp', 'group.fs.mv',
|
|
211
|
+
'group.fs.set_acl', 'group.fs.remove_acl',
|
|
212
|
+
'group.fs.mount', 'group.fs.umount',
|
|
213
|
+
'group.fs.check_upload', 'group.fs.create_upload_session',
|
|
214
|
+
'group.fs.complete_upload', 'group.fs.create_download_ticket',
|
|
215
|
+
'collab.create', 'collab.commit', 'collab.clone',
|
|
216
|
+
'collab.prune', 'collab.unregister',
|
|
217
|
+
'collab.tag.create', 'collab.tag.restore',
|
|
218
|
+
'collab.tag.rm', 'collab.tag.prune',
|
|
193
219
|
'group.commit_state',
|
|
194
220
|
'group.ban', 'group.unban',
|
|
195
221
|
'group.dissolve', 'group.suspend', 'group.resume',
|
|
@@ -513,6 +539,11 @@ export class AUNClient {
|
|
|
513
539
|
_delivery;
|
|
514
540
|
_v2E2EE;
|
|
515
541
|
_groupState;
|
|
542
|
+
_storage;
|
|
543
|
+
_collab;
|
|
544
|
+
_message;
|
|
545
|
+
_group;
|
|
546
|
+
_stream;
|
|
516
547
|
constructor(aid) {
|
|
517
548
|
if (aid !== null && aid !== undefined && !isAIDObject(aid)) {
|
|
518
549
|
throw new ValidationError('AUNClient only accepts an AID object or no argument');
|
|
@@ -629,10 +660,8 @@ export class AUNClient {
|
|
|
629
660
|
this._dispatcher.subscribe('_raw.group.message_created', (data) => this._onRawGroupMessageCreated(data));
|
|
630
661
|
// 群组消息撤回推送:在线 push 通道,与 pull 双 tombstone 兜底互补(SDK 去重只回调一次)
|
|
631
662
|
this._dispatcher.subscribe('_raw.group.message_recalled', (data) => this._safeAsync(this._onRawGroupMessageRecalled(data)));
|
|
632
|
-
//
|
|
663
|
+
// 群组变更事件:透传并触发 V2 state/SPK 维护
|
|
633
664
|
this._dispatcher.subscribe('_raw.group.changed', (data) => this._onRawGroupChanged(data));
|
|
634
|
-
// V2 epoch 轮换事件:清 bootstrap 缓存并触发 SPK rotation
|
|
635
|
-
this._dispatcher.subscribe('_raw.group.v2.epoch_rotated', (data) => this._safeAsync(this._onV2EpochRotated(data)));
|
|
636
665
|
// V2 state proposal 服务平面事件:owner/admin 负责确认或重新提案
|
|
637
666
|
this._dispatcher.subscribe('_raw.group.v2.state_proposed', (data) => this._safeAsync(this._onV2StateProposed(data)));
|
|
638
667
|
this._dispatcher.subscribe('_raw.group.v2.state_retry_needed', (data) => this._safeAsync(this._onV2StateRetryNeeded(data)));
|
|
@@ -651,6 +680,36 @@ export class AUNClient {
|
|
|
651
680
|
get aid() {
|
|
652
681
|
return this._aid;
|
|
653
682
|
}
|
|
683
|
+
/** AUN Storage VFS 入口 */
|
|
684
|
+
get storage() {
|
|
685
|
+
if (!this._storage)
|
|
686
|
+
this._storage = new StorageVFS(this);
|
|
687
|
+
return this._storage;
|
|
688
|
+
}
|
|
689
|
+
/** AUN Collab 入口 */
|
|
690
|
+
get collab() {
|
|
691
|
+
if (!this._collab)
|
|
692
|
+
this._collab = new CollabClient(this);
|
|
693
|
+
return this._collab;
|
|
694
|
+
}
|
|
695
|
+
/** Message facade 入口 */
|
|
696
|
+
get message() {
|
|
697
|
+
if (!this._message)
|
|
698
|
+
this._message = new MessageFacade(this);
|
|
699
|
+
return this._message;
|
|
700
|
+
}
|
|
701
|
+
/** Group facade 入口 */
|
|
702
|
+
get group() {
|
|
703
|
+
if (!this._group)
|
|
704
|
+
this._group = new GroupFacade(this);
|
|
705
|
+
return this._group;
|
|
706
|
+
}
|
|
707
|
+
/** Stream facade 入口 */
|
|
708
|
+
get stream() {
|
|
709
|
+
if (!this._stream)
|
|
710
|
+
this._stream = new StreamFacade(this);
|
|
711
|
+
return this._stream;
|
|
712
|
+
}
|
|
654
713
|
/** 当前 AID 值对象 */
|
|
655
714
|
get currentAid() {
|
|
656
715
|
return this._currentAid;
|
|
@@ -896,7 +955,369 @@ export class AUNClient {
|
|
|
896
955
|
* 自动处理内部方法限制、E2EE 加解密、客户端签名等。
|
|
897
956
|
*/
|
|
898
957
|
async call(method, params) {
|
|
899
|
-
|
|
958
|
+
try {
|
|
959
|
+
return await this._rpcPipeline.call(method, params);
|
|
960
|
+
}
|
|
961
|
+
catch (err) {
|
|
962
|
+
await this._maybeHandleHalfOpenRpcFailure(err);
|
|
963
|
+
throw err;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
async _maybeHandleHalfOpenRpcFailure(err) {
|
|
967
|
+
if (this._closing || this.state !== ConnectionState.READY)
|
|
968
|
+
return;
|
|
969
|
+
if (!this._sessionOptions?.auto_reconnect || this._reconnectActive)
|
|
970
|
+
return;
|
|
971
|
+
if (!(err instanceof ConnectionError) && !(err instanceof TimeoutError))
|
|
972
|
+
return;
|
|
973
|
+
await this._handleTransportDisconnect(err);
|
|
974
|
+
}
|
|
975
|
+
async createGroup(params = {}) {
|
|
976
|
+
if (!isJsonObject(params)) {
|
|
977
|
+
throw new ValidationError('createGroup params must be an object');
|
|
978
|
+
}
|
|
979
|
+
const payload = { ...params };
|
|
980
|
+
const groupName = String(payload.group_name ?? payload.groupName ?? '').trim();
|
|
981
|
+
if (!groupName) {
|
|
982
|
+
return await this.call('group.create', payload);
|
|
983
|
+
}
|
|
984
|
+
const keyPair = new CryptoProvider().generateIdentity();
|
|
985
|
+
payload.public_key = keyPair.public_key_der_b64;
|
|
986
|
+
payload.curve = keyPair.curve;
|
|
987
|
+
const result = await this.call('group.create', payload);
|
|
988
|
+
const resultMap = isJsonObject(result) ? result : {};
|
|
989
|
+
const group = isJsonObject(resultMap.group) ? resultMap.group : {};
|
|
990
|
+
const aidCert = isJsonObject(resultMap.aid_cert) ? resultMap.aid_cert : {};
|
|
991
|
+
const groupAid = String(group.group_aid ?? resultMap.group_aid ?? '').trim();
|
|
992
|
+
const certPem = String(aidCert.cert ?? aidCert.cert_pem ?? resultMap.cert ?? resultMap.cert_pem ?? '').trim();
|
|
993
|
+
if (!groupAid || !certPem) {
|
|
994
|
+
throw new ValidationError('group.create named group response missing group.group_aid or aid_cert.cert');
|
|
995
|
+
}
|
|
996
|
+
const store = new AIDStore({
|
|
997
|
+
aunPath: this._configModel.aunPath,
|
|
998
|
+
encryptionSeed: this._configModel.seedPassword ?? '',
|
|
999
|
+
slotId: this._slotId,
|
|
1000
|
+
verifySsl: this._configModel.verifySsl,
|
|
1001
|
+
rootCaPath: this._configModel.rootCaPath,
|
|
1002
|
+
debug: this._configModel.debug,
|
|
1003
|
+
});
|
|
1004
|
+
try {
|
|
1005
|
+
const imported = store.importGroupIdentity(groupAid, {
|
|
1006
|
+
private_key_pem: keyPair.private_key_pem,
|
|
1007
|
+
public_key_der_b64: keyPair.public_key_der_b64,
|
|
1008
|
+
curve: keyPair.curve,
|
|
1009
|
+
cert_pem: certPem,
|
|
1010
|
+
});
|
|
1011
|
+
if (!imported.ok) {
|
|
1012
|
+
throw new ValidationError(imported.error.message);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
finally {
|
|
1016
|
+
store.close();
|
|
1017
|
+
}
|
|
1018
|
+
return result;
|
|
1019
|
+
}
|
|
1020
|
+
async bindGroupAid(params = {}, options = {}) {
|
|
1021
|
+
if (!isJsonObject(params)) {
|
|
1022
|
+
throw new ValidationError('bindGroupAid params must be an object');
|
|
1023
|
+
}
|
|
1024
|
+
const store = options.aidStore;
|
|
1025
|
+
if (!store) {
|
|
1026
|
+
throw new ValidationError('bindGroupAid requires aidStore');
|
|
1027
|
+
}
|
|
1028
|
+
const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
|
|
1029
|
+
const keystore = store._keystore; // AIDStore._keystore: LocalIdentityStore
|
|
1030
|
+
let keyPair = null;
|
|
1031
|
+
// 优先复用 pending 槽位中已暂存的待绑定密钥(崩溃/重试幂等)
|
|
1032
|
+
if (groupId && keystore && typeof keystore.loadPendingGroupBind === 'function') {
|
|
1033
|
+
keyPair = keystore.loadPendingGroupBind(groupId);
|
|
1034
|
+
}
|
|
1035
|
+
if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
|
|
1036
|
+
// 未命中 pending,生成新密钥
|
|
1037
|
+
const generated = new CryptoProvider().generateIdentity();
|
|
1038
|
+
keyPair = {
|
|
1039
|
+
private_key_pem: generated.private_key_pem,
|
|
1040
|
+
public_key_der_b64: generated.public_key_der_b64,
|
|
1041
|
+
curve: generated.curve,
|
|
1042
|
+
};
|
|
1043
|
+
// 发 RPC 前先落盘暂存,确保崩溃后重试能复用同一密钥
|
|
1044
|
+
if (groupId && keystore && typeof keystore.savePendingGroupBind === 'function') {
|
|
1045
|
+
keystore.savePendingGroupBind(groupId, keyPair);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (!keyPair) {
|
|
1049
|
+
throw new ValidationError('bindGroupAid: failed to generate or load key pair');
|
|
1050
|
+
}
|
|
1051
|
+
const payload = { ...params };
|
|
1052
|
+
if (!String(payload.group_id ?? '').trim() && groupId) {
|
|
1053
|
+
payload.group_id = groupId;
|
|
1054
|
+
}
|
|
1055
|
+
payload.public_key = keyPair.public_key_der_b64;
|
|
1056
|
+
payload.curve = keyPair.curve;
|
|
1057
|
+
const result = await this.call('group.bind_group_aid', payload);
|
|
1058
|
+
const resultMap = isJsonObject(result) ? result : {};
|
|
1059
|
+
const group = isJsonObject(resultMap.group) ? resultMap.group : {};
|
|
1060
|
+
const aidCert = isJsonObject(resultMap.aid_cert) ? resultMap.aid_cert : {};
|
|
1061
|
+
const groupAid = String(group.group_aid ?? resultMap.group_aid ?? '').trim();
|
|
1062
|
+
const certPem = String(aidCert.cert ?? aidCert.cert_pem ?? resultMap.cert ?? resultMap.cert_pem ?? '').trim();
|
|
1063
|
+
if (!groupAid || !certPem) {
|
|
1064
|
+
throw new ValidationError('group.bind_group_aid response missing group.group_aid or aid_cert.cert');
|
|
1065
|
+
}
|
|
1066
|
+
// 证书公钥校验:确保服务端返回的证书公钥 == 本地公钥
|
|
1067
|
+
await this.verifyCertPublicKeyMatch(certPem, keyPair.public_key_der_b64, 'bindGroupAid');
|
|
1068
|
+
const imported = store.importGroupIdentity(groupAid, {
|
|
1069
|
+
private_key_pem: keyPair.private_key_pem,
|
|
1070
|
+
public_key_der_b64: keyPair.public_key_der_b64,
|
|
1071
|
+
curve: keyPair.curve,
|
|
1072
|
+
cert_pem: certPem,
|
|
1073
|
+
});
|
|
1074
|
+
if (!imported.ok) {
|
|
1075
|
+
throw new ValidationError(imported.error.message);
|
|
1076
|
+
}
|
|
1077
|
+
// 落盘成功,清除 pending 槽位
|
|
1078
|
+
if (groupId && keystore && typeof keystore.clearPendingGroupBind === 'function') {
|
|
1079
|
+
keystore.clearPendingGroupBind(groupId);
|
|
1080
|
+
}
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
1083
|
+
async startGroupTransfer(params = {}, options = {}) {
|
|
1084
|
+
if (!isJsonObject(params)) {
|
|
1085
|
+
throw new ValidationError('startGroupTransfer params must be an object');
|
|
1086
|
+
}
|
|
1087
|
+
const store = options.aidStore;
|
|
1088
|
+
if (!store) {
|
|
1089
|
+
throw new ValidationError('startGroupTransfer requires aidStore');
|
|
1090
|
+
}
|
|
1091
|
+
const groupId = String(params.group_id ?? '').trim();
|
|
1092
|
+
const newOwner = String(params.new_owner ?? '').trim();
|
|
1093
|
+
if (!groupId || !newOwner) {
|
|
1094
|
+
throw new ValidationError('startGroupTransfer requires group_id and new_owner');
|
|
1095
|
+
}
|
|
1096
|
+
let groupAid = String(params.group_aid ?? '').trim();
|
|
1097
|
+
if (!groupAid) {
|
|
1098
|
+
const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
|
|
1099
|
+
const infoMap = isJsonObject(info) ? info : {};
|
|
1100
|
+
const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
|
|
1101
|
+
groupAid = String(info?.group_aid ?? '').trim();
|
|
1102
|
+
}
|
|
1103
|
+
if (!groupAid) {
|
|
1104
|
+
throw new ValidationError('startGroupTransfer: unable to determine group_aid');
|
|
1105
|
+
}
|
|
1106
|
+
const loaded = store.load(groupAid);
|
|
1107
|
+
if (!loaded.ok || !loaded.data) {
|
|
1108
|
+
throw new ValidationError(`startGroupTransfer: group_aid identity not found: ${groupAid}`);
|
|
1109
|
+
}
|
|
1110
|
+
const aidObj = loaded.data.aid;
|
|
1111
|
+
if (!aidObj.isPrivateKeyValid() || !aidObj.privateKeyPem) {
|
|
1112
|
+
throw new ValidationError(`startGroupTransfer: group_aid private key not found: ${groupAid}`);
|
|
1113
|
+
}
|
|
1114
|
+
const nonce = crypto.randomUUID().replace(/-/g, '');
|
|
1115
|
+
const issuedMs = Date.now();
|
|
1116
|
+
const canonical = [
|
|
1117
|
+
'aun-group-owner-transfer-v1',
|
|
1118
|
+
groupId.toLowerCase(),
|
|
1119
|
+
groupAid.toLowerCase(),
|
|
1120
|
+
newOwner.toLowerCase(),
|
|
1121
|
+
nonce,
|
|
1122
|
+
String(issuedMs),
|
|
1123
|
+
].join('|');
|
|
1124
|
+
const signed = aidObj.sign(canonical);
|
|
1125
|
+
if (!signed.ok) {
|
|
1126
|
+
throw new ValidationError(`startGroupTransfer: sign failed: ${signed.error.message}`);
|
|
1127
|
+
}
|
|
1128
|
+
const payload = { ...params, group_aid: groupAid };
|
|
1129
|
+
payload.transfer_auth = { nonce, issued_ms: issuedMs, signature: signed.data.signature };
|
|
1130
|
+
return await this.call('group.transfer_owner', payload);
|
|
1131
|
+
}
|
|
1132
|
+
async renewGroupAid(params = {}, options = {}) {
|
|
1133
|
+
if (!isJsonObject(params)) {
|
|
1134
|
+
throw new ValidationError('renewGroupAid params must be an object');
|
|
1135
|
+
}
|
|
1136
|
+
const store = options.aidStore;
|
|
1137
|
+
if (!store) {
|
|
1138
|
+
throw new ValidationError('renewGroupAid requires aidStore');
|
|
1139
|
+
}
|
|
1140
|
+
const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
|
|
1141
|
+
if (!groupId) {
|
|
1142
|
+
throw new ValidationError('renewGroupAid requires group_id or group_aid');
|
|
1143
|
+
}
|
|
1144
|
+
let groupAid = String(params.group_aid ?? '').trim();
|
|
1145
|
+
if (!groupAid) {
|
|
1146
|
+
const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
|
|
1147
|
+
const infoMap = isJsonObject(info) ? info : {};
|
|
1148
|
+
const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
|
|
1149
|
+
groupAid = String(info?.group_aid ?? '').trim();
|
|
1150
|
+
}
|
|
1151
|
+
if (!groupAid) {
|
|
1152
|
+
throw new ValidationError('renewGroupAid: unable to determine group_aid');
|
|
1153
|
+
}
|
|
1154
|
+
// 加载旧 group_aid 私钥(用于签名授权轮换)
|
|
1155
|
+
const loaded = store.load(groupAid);
|
|
1156
|
+
if (!loaded.ok || !loaded.data) {
|
|
1157
|
+
throw new ValidationError(`renewGroupAid: group_aid identity not found: ${groupAid}`);
|
|
1158
|
+
}
|
|
1159
|
+
const oldAidObj = loaded.data.aid;
|
|
1160
|
+
if (!oldAidObj.isPrivateKeyValid() || !oldAidObj.privateKeyPem) {
|
|
1161
|
+
throw new ValidationError(`renewGroupAid: group_aid has no private key: ${groupAid}`);
|
|
1162
|
+
}
|
|
1163
|
+
const oldPublicKey = oldAidObj.publicKey;
|
|
1164
|
+
if (!oldPublicKey) {
|
|
1165
|
+
throw new ValidationError(`renewGroupAid: cannot determine old public key for ${groupAid}`);
|
|
1166
|
+
}
|
|
1167
|
+
// 生成新密钥
|
|
1168
|
+
const newKeyPair = new CryptoProvider().generateIdentity();
|
|
1169
|
+
const newPublicKey = newKeyPair.public_key_der_b64;
|
|
1170
|
+
const newPrivateKey = newKeyPair.private_key_pem;
|
|
1171
|
+
const curve = newKeyPair.curve || 'P-256';
|
|
1172
|
+
if (!newPublicKey || !newPrivateKey) {
|
|
1173
|
+
throw new ValidationError('renewGroupAid: generated incomplete group identity');
|
|
1174
|
+
}
|
|
1175
|
+
// 用旧私钥对 canonical payload 签名
|
|
1176
|
+
const nonce = crypto.randomUUID().replace(/-/g, '');
|
|
1177
|
+
const issuedMs = Date.now();
|
|
1178
|
+
const oldHash = crypto.createHash('sha256').update(oldPublicKey).digest('hex');
|
|
1179
|
+
const newHash = crypto.createHash('sha256').update(newPublicKey).digest('hex');
|
|
1180
|
+
const canonical = [
|
|
1181
|
+
'aun-group-aid-renew-v1',
|
|
1182
|
+
groupId.toLowerCase(),
|
|
1183
|
+
groupAid.toLowerCase(),
|
|
1184
|
+
oldHash,
|
|
1185
|
+
newHash,
|
|
1186
|
+
nonce,
|
|
1187
|
+
String(issuedMs),
|
|
1188
|
+
].join('|');
|
|
1189
|
+
const signed = oldAidObj.sign(canonical);
|
|
1190
|
+
if (!signed.ok) {
|
|
1191
|
+
throw new ValidationError(`renewGroupAid: sign failed: ${signed.error.message}`);
|
|
1192
|
+
}
|
|
1193
|
+
const payload = { ...params };
|
|
1194
|
+
if (!String(payload.group_id ?? '').trim()) {
|
|
1195
|
+
payload.group_id = groupId;
|
|
1196
|
+
}
|
|
1197
|
+
payload.group_aid = groupAid;
|
|
1198
|
+
payload.old_public_key = oldPublicKey;
|
|
1199
|
+
payload.new_public_key = newPublicKey;
|
|
1200
|
+
payload.curve = curve;
|
|
1201
|
+
payload.renew_proof = { nonce, issued_ms: issuedMs, signature: signed.data.signature };
|
|
1202
|
+
const result = await this.call('group.renew_group_aid', payload);
|
|
1203
|
+
const resultMap = isJsonObject(result) ? result : {};
|
|
1204
|
+
const group = isJsonObject(resultMap.group) ? resultMap.group : {};
|
|
1205
|
+
const aidCert = isJsonObject(resultMap.aid_cert) ? resultMap.aid_cert : {};
|
|
1206
|
+
const returnedGroupAid = String(group.group_aid ?? resultMap.group_aid ?? groupAid).trim();
|
|
1207
|
+
const certPem = String(aidCert.cert ?? aidCert.cert_pem ?? resultMap.cert ?? resultMap.cert_pem ?? '').trim();
|
|
1208
|
+
if (!returnedGroupAid || !certPem) {
|
|
1209
|
+
throw new ValidationError('renewGroupAid response missing group.group_aid or aid_cert.cert');
|
|
1210
|
+
}
|
|
1211
|
+
// 证书公钥校验:确保服务端返回的新证书公钥 == 本地新公钥
|
|
1212
|
+
await this.verifyCertPublicKeyMatch(certPem, newPublicKey, 'renewGroupAid');
|
|
1213
|
+
// 用新密钥覆盖本地身份(合法轮换)
|
|
1214
|
+
const imported = store.importGroupIdentity(returnedGroupAid, {
|
|
1215
|
+
private_key_pem: newPrivateKey,
|
|
1216
|
+
public_key_der_b64: newPublicKey,
|
|
1217
|
+
curve,
|
|
1218
|
+
cert_pem: certPem,
|
|
1219
|
+
});
|
|
1220
|
+
if (!imported.ok) {
|
|
1221
|
+
throw new ValidationError(`renewGroupAid failed to persist group identity: ${imported.error.message}`);
|
|
1222
|
+
}
|
|
1223
|
+
return result;
|
|
1224
|
+
}
|
|
1225
|
+
async completeGroupTransfer(params = {}, options = {}) {
|
|
1226
|
+
if (!isJsonObject(params)) {
|
|
1227
|
+
throw new ValidationError('completeGroupTransfer params must be an object');
|
|
1228
|
+
}
|
|
1229
|
+
const store = options.aidStore;
|
|
1230
|
+
if (!store) {
|
|
1231
|
+
throw new ValidationError('completeGroupTransfer requires aidStore');
|
|
1232
|
+
}
|
|
1233
|
+
const keyPair = new CryptoProvider().generateIdentity();
|
|
1234
|
+
const payload = { ...params };
|
|
1235
|
+
const groupId = String(params.group_id ?? '').trim();
|
|
1236
|
+
if (!groupId) {
|
|
1237
|
+
throw new ValidationError('completeGroupTransfer requires group_id');
|
|
1238
|
+
}
|
|
1239
|
+
let groupAid = String(params.group_aid ?? '').trim();
|
|
1240
|
+
if (!groupAid) {
|
|
1241
|
+
const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
|
|
1242
|
+
const infoMap = isJsonObject(info) ? info : {};
|
|
1243
|
+
const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
|
|
1244
|
+
groupAid = String(info?.group_aid ?? '').trim();
|
|
1245
|
+
}
|
|
1246
|
+
if (!groupAid) {
|
|
1247
|
+
throw new ValidationError('completeGroupTransfer: unable to determine group_aid');
|
|
1248
|
+
}
|
|
1249
|
+
const current = this.currentAid;
|
|
1250
|
+
const newOwner = String(current?.aid ?? '').trim();
|
|
1251
|
+
if (!current || !newOwner || !current.isPrivateKeyValid()) {
|
|
1252
|
+
throw new ValidationError('completeGroupTransfer requires current new-owner AID with private key');
|
|
1253
|
+
}
|
|
1254
|
+
const nonce = crypto.randomUUID().replace(/-/g, '');
|
|
1255
|
+
const issuedMs = Date.now();
|
|
1256
|
+
const publicKeyHash = crypto.createHash('sha256').update(keyPair.public_key_der_b64, 'utf8').digest('hex');
|
|
1257
|
+
const canonical = [
|
|
1258
|
+
'aun-group-owner-transfer-accept-v1',
|
|
1259
|
+
groupId.toLowerCase(),
|
|
1260
|
+
groupAid.toLowerCase(),
|
|
1261
|
+
newOwner.toLowerCase(),
|
|
1262
|
+
publicKeyHash,
|
|
1263
|
+
nonce,
|
|
1264
|
+
String(issuedMs),
|
|
1265
|
+
].join('|');
|
|
1266
|
+
const signed = current.sign(canonical);
|
|
1267
|
+
if (!signed.ok) {
|
|
1268
|
+
throw new ValidationError(`completeGroupTransfer: accept sign failed: ${signed.error.message}`);
|
|
1269
|
+
}
|
|
1270
|
+
payload.group_aid = groupAid;
|
|
1271
|
+
payload.public_key = keyPair.public_key_der_b64;
|
|
1272
|
+
payload.curve = keyPair.curve;
|
|
1273
|
+
payload.transfer_accept = { nonce, issued_ms: issuedMs, signature: signed.data.signature };
|
|
1274
|
+
const result = await this.call('group.complete_transfer', payload);
|
|
1275
|
+
const resultMap = isJsonObject(result) ? result : {};
|
|
1276
|
+
const group = isJsonObject(resultMap.group) ? resultMap.group : {};
|
|
1277
|
+
const aidCert = isJsonObject(resultMap.aid_cert) ? resultMap.aid_cert : {};
|
|
1278
|
+
const returnedGroupAid = String(group.group_aid ?? resultMap.group_aid ?? '').trim();
|
|
1279
|
+
const certPem = String(aidCert.cert ?? aidCert.cert_pem ?? resultMap.cert ?? resultMap.cert_pem ?? '').trim();
|
|
1280
|
+
if (!returnedGroupAid || !certPem) {
|
|
1281
|
+
throw new ValidationError('group.complete_transfer response missing group.group_aid or aid_cert.cert');
|
|
1282
|
+
}
|
|
1283
|
+
// 证书公钥校验:确保服务端返回的证书公钥 == 本地公钥
|
|
1284
|
+
await this.verifyCertPublicKeyMatch(certPem, keyPair.public_key_der_b64, 'completeGroupTransfer');
|
|
1285
|
+
const imported = store.importGroupIdentity(returnedGroupAid, {
|
|
1286
|
+
private_key_pem: keyPair.private_key_pem,
|
|
1287
|
+
public_key_der_b64: keyPair.public_key_der_b64,
|
|
1288
|
+
curve: keyPair.curve,
|
|
1289
|
+
cert_pem: certPem,
|
|
1290
|
+
});
|
|
1291
|
+
if (!imported.ok) {
|
|
1292
|
+
throw new ValidationError(imported.error.message);
|
|
1293
|
+
}
|
|
1294
|
+
return result;
|
|
1295
|
+
}
|
|
1296
|
+
/** 证书公钥校验:确保服务端返回的证书公钥 == 本地公钥(DER base64) */
|
|
1297
|
+
async verifyCertPublicKeyMatch(certPem, localPublicKeyDerB64, operation) {
|
|
1298
|
+
// 简化实现:使用 Node.js crypto 或浏览器 Web Crypto API
|
|
1299
|
+
// 完整实现需要解析 X.509 证书提取 subjectPublicKeyInfo
|
|
1300
|
+
// 这里先做基本校验,后续可增强
|
|
1301
|
+
const crypto = globalThis.crypto;
|
|
1302
|
+
if (!crypto || !crypto.subtle) {
|
|
1303
|
+
// 浏览器环境或 Node < 15,跳过校验(非致命)
|
|
1304
|
+
console.warn(`${operation}: crypto.subtle not available, skipping cert public key verification`);
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
try {
|
|
1308
|
+
// TODO: 完整实现需要 ASN.1 解析库提取证书公钥
|
|
1309
|
+
// 当前仅做导入测试,确保证书格式有效
|
|
1310
|
+
const certLines = certPem.split('\n').filter(l => l && !l.startsWith('-----'));
|
|
1311
|
+
const certDer = Buffer.from(certLines.join(''), 'base64');
|
|
1312
|
+
// 验证本地公钥可导入
|
|
1313
|
+
const localPubDer = Buffer.from(localPublicKeyDerB64, 'base64');
|
|
1314
|
+
await crypto.subtle.importKey('spki', localPubDer, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
|
1315
|
+
// 证书公钥提取需要完整 ASN.1 解析,暂时依赖服务端正确性
|
|
1316
|
+
// 后续迭代可引入 @peculiar/x509 或类似库
|
|
1317
|
+
}
|
|
1318
|
+
catch (err) {
|
|
1319
|
+
throw new AuthError(`${operation}: certificate or public key validation failed: ${err.message}`);
|
|
1320
|
+
}
|
|
900
1321
|
}
|
|
901
1322
|
static _notifyParamsSizeOk(params) {
|
|
902
1323
|
return new TextEncoder().encode(JSON.stringify(params)).length <= MAX_NOTIFY_PAYLOAD_SIZE;
|
|
@@ -932,7 +1353,7 @@ export class AUNClient {
|
|
|
932
1353
|
throw new ValidationError('notify payload is too large');
|
|
933
1354
|
}
|
|
934
1355
|
const targetAid = String(options.to ?? '').trim();
|
|
935
|
-
const targetGroupId = String(options.group_id ?? options.groupId ?? '').trim();
|
|
1356
|
+
const targetGroupId = String(options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId ?? '').trim();
|
|
936
1357
|
const targetDeviceId = String(options.device_id ?? options.deviceId ?? '').trim();
|
|
937
1358
|
const targetSlotId = String(options.slot_id ?? options.slotId ?? '').trim();
|
|
938
1359
|
const ttl = AUNClient._normalizeNotifyTtl(options.ttl_ms ?? options.ttlMs);
|
|
@@ -942,6 +1363,13 @@ export class AUNClient {
|
|
|
942
1363
|
if (targetSlotId && !targetDeviceId) {
|
|
943
1364
|
throw new ValidationError('slot_id requires device_id for notify target');
|
|
944
1365
|
}
|
|
1366
|
+
// 校验 AID 和 Group ID 格式
|
|
1367
|
+
if (targetAid) {
|
|
1368
|
+
validateAIDFormat(targetAid, 'to');
|
|
1369
|
+
}
|
|
1370
|
+
if (targetGroupId) {
|
|
1371
|
+
validateGroupIDFormat(targetGroupId, 'group_id');
|
|
1372
|
+
}
|
|
945
1373
|
if (targetAid) {
|
|
946
1374
|
const eventMethod = AUNClient._validateNotifyEventMethod(method);
|
|
947
1375
|
const target = { type: 'aid', aid: targetAid };
|
|
@@ -965,6 +1393,7 @@ export class AUNClient {
|
|
|
965
1393
|
throw new ValidationError('group_id is required for group notify');
|
|
966
1394
|
}
|
|
967
1395
|
const routeParams = {
|
|
1396
|
+
group_aid: normalizedGroupId,
|
|
968
1397
|
group_id: normalizedGroupId,
|
|
969
1398
|
deliver: { method: eventMethod, params: payload },
|
|
970
1399
|
};
|
|
@@ -999,8 +1428,11 @@ export class AUNClient {
|
|
|
999
1428
|
delete p._skip_auto_ack;
|
|
1000
1429
|
delete p.skip_auto_ack;
|
|
1001
1430
|
delete p._group_cursor_params;
|
|
1002
|
-
if (method.startsWith('group.') && p.
|
|
1003
|
-
p.
|
|
1431
|
+
if (method.startsWith('group.') && p.group_aid !== undefined && p.group_aid !== null) {
|
|
1432
|
+
p.group_aid = normalizeGroupId(String(p.group_aid)) || String(p.group_aid);
|
|
1433
|
+
}
|
|
1434
|
+
else if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
|
|
1435
|
+
p.group_aid = normalizeGroupId(String(p.group_id)) || String(p.group_id);
|
|
1004
1436
|
}
|
|
1005
1437
|
if (method.startsWith('group.') && p.device_id === undefined) {
|
|
1006
1438
|
p.device_id = this._deviceId;
|
|
@@ -1160,7 +1592,13 @@ export class AUNClient {
|
|
|
1160
1592
|
_messageEnvelopeFieldsForDebug(message) {
|
|
1161
1593
|
return this._delivery.messageEnvelopeFieldsForDebug(message);
|
|
1162
1594
|
}
|
|
1595
|
+
_isMessageDebugEnabled() {
|
|
1596
|
+
const checker = this._clientLog.isDebugEnabled;
|
|
1597
|
+
return typeof checker === 'function' ? checker.call(this._clientLog) : true;
|
|
1598
|
+
}
|
|
1163
1599
|
_logMessageDebug(stage, source, event, message, opts = {}) {
|
|
1600
|
+
if (!this._isMessageDebugEnabled())
|
|
1601
|
+
return;
|
|
1164
1602
|
this._delivery.logMessageDebug(stage, source, event, message, opts);
|
|
1165
1603
|
}
|
|
1166
1604
|
_tryAcquirePullGate(key) {
|
|
@@ -1295,21 +1733,21 @@ export class AUNClient {
|
|
|
1295
1733
|
}
|
|
1296
1734
|
_extractGroupIdFromResult(result) {
|
|
1297
1735
|
const group = isJsonObject(result.group) ? result.group : null;
|
|
1298
|
-
const gid = group ? String(group.group_id ?? '') : '';
|
|
1736
|
+
const gid = group ? String(group.group_aid ?? group.group_id ?? '') : '';
|
|
1299
1737
|
if (gid)
|
|
1300
1738
|
return gid;
|
|
1301
|
-
const directGid = String(result.group_id ?? '');
|
|
1739
|
+
const directGid = String(result.group_aid ?? result.group_id ?? '');
|
|
1302
1740
|
if (directGid)
|
|
1303
1741
|
return directGid;
|
|
1304
1742
|
const member = isJsonObject(result.member) ? result.member : null;
|
|
1305
|
-
return member ? String(member.group_id ?? '') : '';
|
|
1743
|
+
return member ? String(member.group_aid ?? member.group_id ?? '') : '';
|
|
1306
1744
|
}
|
|
1307
1745
|
async _onRawGroupChanged(data) {
|
|
1308
1746
|
const tStart = Date.now();
|
|
1309
1747
|
try {
|
|
1310
1748
|
if (isJsonObject(data)) {
|
|
1311
1749
|
const d = data;
|
|
1312
|
-
const groupId = String(d.group_id ?? '');
|
|
1750
|
+
const groupId = String(d.group_aid ?? d.group_id ?? '');
|
|
1313
1751
|
const action = String(d.action ?? '');
|
|
1314
1752
|
this._clientLog.debug(`_onRawGroupChanged enter: group_id=${groupId}, action=${action}, event_seq=${String(d.event_seq ?? '')}`);
|
|
1315
1753
|
// 验签:有 client_signature 就验,没有默认安全(H20: 严格 boolean)
|
|
@@ -1347,18 +1785,22 @@ export class AUNClient {
|
|
|
1347
1785
|
this._v2E2EE.deleteBootstrapCacheEntry(`group:${groupId}`);
|
|
1348
1786
|
this._v2GroupSecurityLevels.delete(groupId);
|
|
1349
1787
|
this._v2StateChains.delete(groupId);
|
|
1350
|
-
|
|
1351
|
-
|
|
1788
|
+
const groupNs = `group:${groupId}`;
|
|
1789
|
+
const groupEventNs = `group_event:${groupId}`;
|
|
1790
|
+
this._seqTracker.removeNamespace(groupNs);
|
|
1791
|
+
this._seqTracker.removeNamespace(groupEventNs);
|
|
1792
|
+
this._persistRepairedSeq(groupNs);
|
|
1793
|
+
this._persistRepairedSeq(groupEventNs);
|
|
1352
1794
|
this._saveSeqTrackerState();
|
|
1353
1795
|
for (const key of this._gapFillDone.keys()) {
|
|
1354
1796
|
if (key.includes(groupId)) {
|
|
1355
1797
|
this._gapFillDone.delete(key);
|
|
1356
1798
|
}
|
|
1357
1799
|
}
|
|
1358
|
-
this._pushedSeqs.delete(
|
|
1359
|
-
this._pushedSeqs.delete(
|
|
1360
|
-
this._pendingOrderedMsgs.delete(
|
|
1361
|
-
this._pendingOrderedMsgs.delete(
|
|
1800
|
+
this._pushedSeqs.delete(groupNs);
|
|
1801
|
+
this._pushedSeqs.delete(groupEventNs);
|
|
1802
|
+
this._pendingOrderedMsgs.delete(groupNs);
|
|
1803
|
+
this._pendingOrderedMsgs.delete(groupEventNs);
|
|
1362
1804
|
this._clientLog.info(`cleaned up disbanded group ${groupId} local state`);
|
|
1363
1805
|
}
|
|
1364
1806
|
/** 同步验签群事件 client_signature。返回 true/false/"pending"。 */
|
|
@@ -1616,6 +2058,7 @@ export class AUNClient {
|
|
|
1616
2058
|
return JSON.stringify([this._aid, this._deviceId, this._slotId]);
|
|
1617
2059
|
}
|
|
1618
2060
|
_resetSeqTrackingState() {
|
|
2061
|
+
this._saveSeqTrackerState();
|
|
1619
2062
|
this._resetV2IdentityRuntime();
|
|
1620
2063
|
this._seqTracker = new SeqTracker();
|
|
1621
2064
|
this._seqTrackerContext = null;
|
|
@@ -1637,6 +2080,7 @@ export class AUNClient {
|
|
|
1637
2080
|
const nextContext = this._currentSeqTrackerContext();
|
|
1638
2081
|
if (nextContext === this._seqTrackerContext)
|
|
1639
2082
|
return;
|
|
2083
|
+
this._saveSeqTrackerState();
|
|
1640
2084
|
this._seqTracker = new SeqTracker();
|
|
1641
2085
|
this._gapFillDone.clear();
|
|
1642
2086
|
this._pushedSeqs.clear();
|
|
@@ -1657,6 +2101,9 @@ export class AUNClient {
|
|
|
1657
2101
|
_saveSeqTrackerState() {
|
|
1658
2102
|
return this._delivery.saveSeqTrackerState();
|
|
1659
2103
|
}
|
|
2104
|
+
_persistSeq(ns, forceSeq) {
|
|
2105
|
+
return this._delivery.persistSeq(ns, forceSeq);
|
|
2106
|
+
}
|
|
1660
2107
|
_persistRepairedSeq(ns) {
|
|
1661
2108
|
return this._delivery.persistRepairedSeq(ns);
|
|
1662
2109
|
}
|
|
@@ -2322,9 +2769,6 @@ export class AUNClient {
|
|
|
2322
2769
|
async _decryptV2PushMessage(data) {
|
|
2323
2770
|
return await this._v2E2EE.decryptV2PushMessage(data);
|
|
2324
2771
|
}
|
|
2325
|
-
async _onV2EpochRotated(data) {
|
|
2326
|
-
return this._v2E2EE.handleV2EpochRotated(data);
|
|
2327
|
-
}
|
|
2328
2772
|
/** 按当前 AID 发现 Gateway;用于 authenticate()/connect() 的新入口。 */
|
|
2329
2773
|
async _resolveGatewayForAid(aid) {
|
|
2330
2774
|
const resolvedAid = String(aid ?? this._aid ?? '').trim();
|
|
@@ -2756,6 +3200,9 @@ export class AUNClient {
|
|
|
2756
3200
|
this._clientLog.warn(`transport disconnected: closeCode=${closeCode ?? 'none'}, error=${error ? formatCaughtError(error) : 'none'}`);
|
|
2757
3201
|
this._state = 'standby';
|
|
2758
3202
|
this._stopBackgroundTasks();
|
|
3203
|
+
void this._transport.close().catch((exc) => {
|
|
3204
|
+
this._clientLog.debug(`transport cleanup skipped: ${formatCaughtError(exc)}`);
|
|
3205
|
+
});
|
|
2759
3206
|
await this._dispatcher.publish('state_change', { state: this._publicState(this._state), error });
|
|
2760
3207
|
if (!this._sessionOptions.auto_reconnect) {
|
|
2761
3208
|
this._reconnectActive = false;
|