@agentunion/fastaun 0.5.20 → 0.5.22
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 +104 -0
- package/_packed_docs/CHANGELOG.md +104 -0
- package/_packed_docs/INDEX.md +59 -34
- package/_packed_docs/KITE_DOCS_GUIDE.md +59 -52
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
- package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
- package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +307 -67
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +41 -15
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +33 -22
- package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
- package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
- package/_packed_docs/sdk/0.5.22-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +212 -0
- package/_packed_docs/sdk/0.5.22-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +199 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +163 -14
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
- package/_packed_docs/sdk/09-group-rpc-manual.md +15 -2
- package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
- package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +73 -0
- package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +90 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +33 -25
- package/_packed_docs/sdk/INDEX.md +67 -33
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +74 -57
- package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +312 -300
- package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +322 -382
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -708
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +28 -20
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +190 -97
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +158 -127
- package/dist/agent-md-schema.js +6 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +22 -2
- package/dist/agent-md.js +92 -68
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.js +50 -15
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +22 -2
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.js +13 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +87 -1
- package/dist/client/delivery.js +1387 -139
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +3 -0
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.js +149 -37
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +12 -2
- package/dist/client/v2-e2ee.js +831 -304
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +412 -68
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +18 -1
- package/dist/events.js +148 -9
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +43 -1
- package/dist/facades.js +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/group-fs.d.ts +1 -0
- package/dist/group-fs.js +37 -19
- package/dist/group-fs.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +6 -1
- package/dist/keystore/aid-db.js +184 -42
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -0
- package/dist/keystore/local-token-store.d.ts +9 -1
- package/dist/keystore/local-token-store.js +79 -14
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +46 -0
- package/dist/org-protocol.js +802 -12
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +36 -5
- package/dist/ownership-v21.js +168 -52
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +24 -2
- package/dist/ownership-workflow.js +682 -41
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/secret-store/file-store.js +84 -0
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.js +2 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +6 -0
- package/dist/storage/lowlevel.js +24 -7
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/read-cache.d.ts +70 -0
- package/dist/storage/read-cache.js +625 -0
- package/dist/storage/read-cache.js.map +1 -0
- package/dist/storage/vfs.d.ts +2 -0
- package/dist/storage/vfs.js +50 -15
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +86 -3
- package/dist/tools/cross-sdk-agent.js +758 -46
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +104 -46
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/v2/e2ee/encrypt-group.js +1 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import * as crypto from 'node:crypto';
|
|
|
5
5
|
import * as fs from 'node:fs';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
9
|
import { URL } from 'node:url';
|
|
9
10
|
import { fileURLToPath } from 'node:url';
|
|
10
11
|
import { AUNClient } from '../client.js';
|
|
@@ -14,6 +15,33 @@ import { publicKeyFingerprint } from '../cert-utils.js';
|
|
|
14
15
|
import { VERSION } from '../version.js';
|
|
15
16
|
import { canonicalJson, digest, buildRequestBinding, verifyMembershipBinding, ProtocolError } from '../org-protocol.js';
|
|
16
17
|
import { ownershipTaskResult } from '../ownership-workflow.js';
|
|
18
|
+
import { TimeoutError } from '../errors.js';
|
|
19
|
+
const credentialProcessStartupId = crypto.randomUUID();
|
|
20
|
+
const cacheReadContext = new AsyncLocalStorage();
|
|
21
|
+
const cacheReadObservers = new WeakMap();
|
|
22
|
+
function acquireCacheReadObserver(target, key, wrap) {
|
|
23
|
+
let observers = cacheReadObservers.get(target);
|
|
24
|
+
if (!observers) {
|
|
25
|
+
observers = new Map();
|
|
26
|
+
cacheReadObservers.set(target, observers);
|
|
27
|
+
}
|
|
28
|
+
let observer = observers.get(key);
|
|
29
|
+
if (!observer) {
|
|
30
|
+
const original = target[key];
|
|
31
|
+
target[key] = wrap(original);
|
|
32
|
+
observer = { users: 0, restore: () => { target[key] = original; } };
|
|
33
|
+
observers.set(key, observer);
|
|
34
|
+
}
|
|
35
|
+
observer.users += 1;
|
|
36
|
+
return () => {
|
|
37
|
+
if (--observer.users === 0) {
|
|
38
|
+
observer.restore();
|
|
39
|
+
observers.delete(key);
|
|
40
|
+
if (!observers.size)
|
|
41
|
+
cacheReadObservers.delete(target);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
17
45
|
function envBool(name, fallback = false) {
|
|
18
46
|
const raw = process.env[name];
|
|
19
47
|
if (raw == null)
|
|
@@ -128,10 +156,32 @@ export class CrossSdkTsAgent {
|
|
|
128
156
|
rawPushObservations = [];
|
|
129
157
|
rawPushObservationId = '';
|
|
130
158
|
rawPushRpcMethods = [];
|
|
159
|
+
eventLedger = [];
|
|
160
|
+
batchEventMode = false;
|
|
161
|
+
batchEvents = [];
|
|
162
|
+
singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
|
|
163
|
+
eventBoundClients = new WeakSet();
|
|
164
|
+
batchEventClient;
|
|
165
|
+
batchEventHandlers = [];
|
|
166
|
+
p2pSubscription = true;
|
|
167
|
+
p2pEventClient;
|
|
168
|
+
p2pEventHandler;
|
|
169
|
+
p2pDeliveryPaused = false;
|
|
170
|
+
p2pDeliveryBlocked = 0;
|
|
171
|
+
p2pDeliveryGate = null;
|
|
172
|
+
p2pDeliveryRetiredClients = new WeakSet();
|
|
173
|
+
rpcSpans = [];
|
|
174
|
+
observationGeneration = 0;
|
|
175
|
+
pullBarrier = null;
|
|
176
|
+
rpcFailures = [];
|
|
177
|
+
messageOperations = new Map();
|
|
131
178
|
rpcObservedTransports = new WeakSet();
|
|
179
|
+
rpcObservedPipelines = new WeakSet();
|
|
180
|
+
rpcObservedPulls = new WeakMap();
|
|
132
181
|
groupChangedEvents = [];
|
|
133
182
|
groupInviteEvents = [];
|
|
134
183
|
readStateEvents = [];
|
|
184
|
+
peersSnapshots = [];
|
|
135
185
|
ownershipEvents = [];
|
|
136
186
|
traces = new Map();
|
|
137
187
|
sendResults = new Map();
|
|
@@ -139,6 +189,18 @@ export class CrossSdkTsAgent {
|
|
|
139
189
|
constructor() {
|
|
140
190
|
this.client = this.newClient();
|
|
141
191
|
}
|
|
192
|
+
credentialStartup;
|
|
193
|
+
credentialProcessId = credentialProcessStartupId;
|
|
194
|
+
credentialProcessCheckpoint() {
|
|
195
|
+
const internal = this.client;
|
|
196
|
+
const metadata = internal._tokenStore._loadMetadataStrict(this.aid) ?? {};
|
|
197
|
+
return structuredClone({ pid: process.pid, startup_uuid: this.credentialProcessId, aid: this.aid,
|
|
198
|
+
cert_fingerprint: this.client.currentAid?.certFingerprint, store: this.aunPath,
|
|
199
|
+
records: metadata.credential_renewals_v1 ?? {}, revocations: metadata.credential_revocations_v1 ?? { version: 1, authorities: {} } });
|
|
200
|
+
}
|
|
201
|
+
captureCredentialStartup() {
|
|
202
|
+
this.credentialStartup ??= { ...this.credentialProcessCheckpoint(), phase: 'before_connect' };
|
|
203
|
+
}
|
|
142
204
|
async start() {
|
|
143
205
|
this.bindClientEvents();
|
|
144
206
|
// Cross-SDK runner 需要先由显式 Tail 建立窗口证明;启动阶段不得遗留后台 Pull。
|
|
@@ -154,29 +216,39 @@ export class CrossSdkTsAgent {
|
|
|
154
216
|
return client;
|
|
155
217
|
}
|
|
156
218
|
bindClientEvents() {
|
|
219
|
+
this.eventBoundClients ??= new WeakSet();
|
|
220
|
+
if (this.eventBoundClients.has(this.client)) {
|
|
221
|
+
this.setP2pSubscription(this.p2pSubscription ?? true);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this.eventBoundClients.add(this.client);
|
|
157
225
|
this.installTransportRpcObserver();
|
|
158
226
|
this.client.on('_raw.message.received', (data) => this.storeRawPushObservation('_raw.message.received', 'p2p', 'legacy', data));
|
|
159
227
|
this.client.on('_raw.peer.v2.message_received', (data) => this.storeRawPushObservation('_raw.peer.v2.message_received', 'p2p', 'v2', data));
|
|
160
228
|
this.client.on('_raw.group.message_created', (data) => this.storeRawPushObservation('_raw.group.message_created', 'group', 'legacy', data));
|
|
161
229
|
this.client.on('_raw.group.v2.message_created', (data) => this.storeRawPushObservation('_raw.group.v2.message_created', 'group', 'v2', data));
|
|
162
230
|
this.client.on('_raw.group.changed', (data) => this.storeRawGroupChangedObservation(data));
|
|
163
|
-
this.client.on('message.received', (msg) => {
|
|
164
|
-
void this.storeInboxItem(this.normalizeMessage(msg, true));
|
|
165
|
-
});
|
|
166
231
|
this.client.on('message.undecryptable', (msg) => {
|
|
167
232
|
void this.storeInboxItem(this.normalizeMessage(msg, false, 'undecryptable'));
|
|
168
233
|
});
|
|
169
234
|
this.client.on('group.message_created', (msg) => {
|
|
170
|
-
|
|
235
|
+
this.countSingleEvent('group.message_created');
|
|
236
|
+
void this.storeApplicationEvent('group.message_created', msg);
|
|
171
237
|
});
|
|
172
238
|
this.client.on('group.message_undecryptable', (msg) => {
|
|
173
239
|
void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, false, 'undecryptable'));
|
|
174
240
|
});
|
|
175
|
-
this.client.on('group.changed', (data) =>
|
|
241
|
+
this.client.on('group.changed', (data) => {
|
|
242
|
+
this.countSingleEvent('group.changed');
|
|
243
|
+
void this.storeApplicationEvent('group.changed', data);
|
|
244
|
+
});
|
|
176
245
|
this.client.on('group.invite_received', (data) => this.storeGroupInviteEvent(data));
|
|
177
246
|
for (const event of ['message.read_changed', 'group.read_changed', 'read_state_sync']) {
|
|
178
247
|
this.client.on(event, (payload) => { this.readStateEvents.push({ event, payload: jsonSafe(payload) }); });
|
|
179
248
|
}
|
|
249
|
+
this.client.on('message.peers_snapshot', (payload) => {
|
|
250
|
+
this.peersSnapshots.push({ event: 'message.peers_snapshot', payload: jsonSafe(payload) });
|
|
251
|
+
});
|
|
180
252
|
for (const event of ['ownership.request', 'ownership.pending', 'ownership.progress', 'ownership.completed', 'ownership.rejected', 'ownership.stopped', 'ownership.error', 'ownership.action_required']) {
|
|
181
253
|
this.client.on(event, (payload) => {
|
|
182
254
|
this.ownershipEvents.push({ event, payload: jsonSafe(payload) });
|
|
@@ -189,15 +261,151 @@ export class CrossSdkTsAgent {
|
|
|
189
261
|
this.recordTrace(textOf(payload.org_id), { stage: event, payload });
|
|
190
262
|
});
|
|
191
263
|
}
|
|
264
|
+
this.setP2pSubscription(this.p2pSubscription ?? true);
|
|
265
|
+
}
|
|
266
|
+
countSingleEvent(event) {
|
|
267
|
+
this.singleEventCounts ??= { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
|
|
268
|
+
this.singleEventCounts[event] = Number(this.singleEventCounts[event] ?? 0) + 1;
|
|
269
|
+
}
|
|
270
|
+
async storeApplicationEvent(event, value) {
|
|
271
|
+
if (event === 'message.received') {
|
|
272
|
+
this.storeEventLedger(event, value);
|
|
273
|
+
await this.storeInboxItem(this.normalizeMessage(value, true));
|
|
274
|
+
}
|
|
275
|
+
else if (event === 'group.message_created') {
|
|
276
|
+
this.storeEventLedger(event, value);
|
|
277
|
+
await this.storeGroupInboxItem(this.normalizeGroupMessage(value, true));
|
|
278
|
+
}
|
|
279
|
+
else if (event === 'group.changed')
|
|
280
|
+
this.storeGroupChangedPublicEvent(value);
|
|
281
|
+
}
|
|
282
|
+
setP2pSubscription(enabled) {
|
|
283
|
+
this.p2pSubscription = enabled;
|
|
284
|
+
if (this.p2pEventHandler && (this.p2pEventClient !== this.client || !enabled)) {
|
|
285
|
+
this.p2pEventClient?.off('message.received', this.p2pEventHandler);
|
|
286
|
+
this.p2pEventHandler = undefined;
|
|
287
|
+
}
|
|
288
|
+
this.p2pEventClient = this.client;
|
|
289
|
+
if (enabled && !this.p2pEventHandler) {
|
|
290
|
+
const client = this.client;
|
|
291
|
+
this.p2pEventHandler = async (msg) => {
|
|
292
|
+
if ((this.p2pDeliveryPaused || this.p2pDeliveryRetiredClients?.has(client)) && !await this.waitForP2pDelivery(client))
|
|
293
|
+
return;
|
|
294
|
+
this.countSingleEvent('message.received');
|
|
295
|
+
await this.storeApplicationEvent('message.received', msg);
|
|
296
|
+
};
|
|
297
|
+
this.client.on('message.received', this.p2pEventHandler);
|
|
298
|
+
}
|
|
299
|
+
this.setBatchEventMode(this.batchEventMode ?? false);
|
|
300
|
+
}
|
|
301
|
+
setP2pDeliveryPaused(paused) {
|
|
302
|
+
this.p2pDeliveryPaused = paused;
|
|
303
|
+
if (!paused) {
|
|
304
|
+
const gate = this.p2pDeliveryGate;
|
|
305
|
+
this.p2pDeliveryGate = null;
|
|
306
|
+
gate?.release();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async waitForP2pDelivery(client) {
|
|
310
|
+
if (this.p2pDeliveryRetiredClients?.has(client))
|
|
311
|
+
return false;
|
|
312
|
+
this.p2pDeliveryBlocked = (this.p2pDeliveryBlocked ?? 0) + 1;
|
|
313
|
+
try {
|
|
314
|
+
while (this.p2pDeliveryPaused && !this.p2pDeliveryRetiredClients?.has(client)) {
|
|
315
|
+
if (!this.p2pDeliveryGate) {
|
|
316
|
+
let release;
|
|
317
|
+
const promise = new Promise((resolve) => { release = resolve; });
|
|
318
|
+
this.p2pDeliveryGate = { promise, release };
|
|
319
|
+
}
|
|
320
|
+
await this.p2pDeliveryGate.promise;
|
|
321
|
+
}
|
|
322
|
+
return !this.p2pDeliveryRetiredClients?.has(client);
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
this.p2pDeliveryBlocked -= 1;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
retireP2pDelivery(client) {
|
|
329
|
+
this.p2pDeliveryRetiredClients ??= new WeakSet();
|
|
330
|
+
this.p2pDeliveryRetiredClients.add(client);
|
|
331
|
+
const gate = this.p2pDeliveryGate;
|
|
332
|
+
this.p2pDeliveryGate = null;
|
|
333
|
+
gate?.release();
|
|
334
|
+
}
|
|
335
|
+
applicationDeliveryState() {
|
|
336
|
+
const dispatcher = this.client._dispatcher;
|
|
337
|
+
return { subscribed: this.p2pSubscription ?? true, paused: this.p2pDeliveryPaused ?? false, blocked: this.p2pDeliveryBlocked ?? 0,
|
|
338
|
+
capacity: dispatcher?._messageQueueCapacity?.() ?? 0, used: dispatcher?._queuedMessageCount ?? 0, waiting: dispatcher?._messageAdmissionWaiters ?? 0 };
|
|
339
|
+
}
|
|
340
|
+
setBatchEventMode(enabled) {
|
|
341
|
+
this.batchEventMode = enabled;
|
|
342
|
+
const events = enabled ? ['message.batch', 'group.message_batch', 'group.event_batch'].filter((event) => event !== 'message.batch' || this.p2pSubscription !== false) : [];
|
|
343
|
+
if (this.batchEventClient === this.client && this.batchEventHandlers?.length === events.length && this.batchEventHandlers.every((item, index) => item.event === events[index]))
|
|
344
|
+
return;
|
|
345
|
+
for (const { event, handler } of this.batchEventHandlers ?? [])
|
|
346
|
+
this.batchEventClient?.off(event, handler);
|
|
347
|
+
this.batchEventClient = this.client;
|
|
348
|
+
this.batchEventHandlers = [];
|
|
349
|
+
const client = this.client;
|
|
350
|
+
for (const event of events) {
|
|
351
|
+
const handler = async (value) => {
|
|
352
|
+
if (event === 'message.batch' && (this.p2pDeliveryPaused || this.p2pDeliveryRetiredClients?.has(client)) && !await this.waitForP2pDelivery(client))
|
|
353
|
+
return;
|
|
354
|
+
const data = jsonObjectOf(value) ?? {};
|
|
355
|
+
const items = JSON.parse(JSON.stringify(jsonSafe(Array.isArray(data.items) ? data.items : [])));
|
|
356
|
+
this.batchEvents ??= [];
|
|
357
|
+
this.batchEvents.push({ observation_id: this.rawPushObservationId, time_ms: Date.now(), event,
|
|
358
|
+
namespace: textOf(data.namespace), source: textOf(data.source), items });
|
|
359
|
+
for (const item of items)
|
|
360
|
+
await this.storeApplicationEvent(textOf(item.event), item.payload);
|
|
361
|
+
};
|
|
362
|
+
this.batchEventHandlers.push({ event, handler });
|
|
363
|
+
this.client.on(event, handler);
|
|
364
|
+
}
|
|
192
365
|
}
|
|
193
366
|
installTransportRpcObserver() {
|
|
367
|
+
const client = this.client;
|
|
368
|
+
const pipeline = client._rpcPipeline;
|
|
369
|
+
this.rpcObservedPipelines ??= new WeakSet();
|
|
370
|
+
this.rpcObservedPulls ??= new WeakMap();
|
|
371
|
+
if (pipeline?.bindActivePullCancellation && !this.rpcObservedPipelines.has(pipeline)) {
|
|
372
|
+
const originalBind = pipeline.bindActivePullCancellation;
|
|
373
|
+
this.rpcObservedPipelines.add(pipeline);
|
|
374
|
+
pipeline.bindActivePullCancellation = (method, params, request, gateKey = '') => {
|
|
375
|
+
const result = originalBind.call(pipeline, method, params, request, gateKey);
|
|
376
|
+
const observed = this.rpcObservedPulls.get(request);
|
|
377
|
+
if (!this.pullBarrier?.preserve || !observed || !['message.pull', 'message.v2.pull'].includes(method)
|
|
378
|
+
|| !textOf(params.sender_aid) || typeof client._delivery?.peerNamespace !== 'function')
|
|
379
|
+
return result;
|
|
380
|
+
const key = gateKey || pipeline.pullGateKeyForCall(method, params);
|
|
381
|
+
const namespace = client._delivery?.peerNamespace(textOf(params.sender_aid));
|
|
382
|
+
const owners = [...pipeline.pullGateStates.values()].flatMap((state) => [...state.activeByNamespace.values()])
|
|
383
|
+
.filter((job) => job.key === key && job.namespace === namespace && job.running && !job.timedOut && !job.invalidated && job.cancel);
|
|
384
|
+
if (owners.length === 1)
|
|
385
|
+
observed.owner = { job: owners[0], cancel: owners[0].cancel, key, namespace: namespace };
|
|
386
|
+
return result;
|
|
387
|
+
};
|
|
388
|
+
}
|
|
194
389
|
const transport = this.client._transport;
|
|
195
390
|
transport?.setTraceMode('diag');
|
|
196
391
|
if (!transport?.call || this.rpcObservedTransports.has(transport))
|
|
197
392
|
return;
|
|
198
393
|
const originalCall = transport.call;
|
|
394
|
+
const originalClose = transport.close;
|
|
199
395
|
this.rpcObservedTransports.add(transport);
|
|
200
|
-
|
|
396
|
+
if (originalClose)
|
|
397
|
+
transport.close = async () => {
|
|
398
|
+
if (!this.pullBarrier?.preserve)
|
|
399
|
+
this.releasePullBarrier();
|
|
400
|
+
try {
|
|
401
|
+
await originalClose.call(transport);
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
if (!this.pullBarrier?.preserve)
|
|
405
|
+
this.releasePullBarrier();
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
transport.call = (method, params, timeout, trace, background) => {
|
|
201
409
|
this.storeRawPushRpcMethod({ type: 'rpc', method, request_window: this.rawPushRequestWindow(params) });
|
|
202
410
|
let requestParams = params;
|
|
203
411
|
if (method === 'auth.connect' && !this.inlineRealtimePayloadV1 && jsonObjectOf(params)) {
|
|
@@ -209,9 +417,220 @@ export class CrossSdkTsAgent {
|
|
|
209
417
|
}
|
|
210
418
|
requestParams = request;
|
|
211
419
|
}
|
|
212
|
-
|
|
420
|
+
const generation = this.observationGeneration;
|
|
421
|
+
const span = this.startRpcSpan(method, requestParams);
|
|
422
|
+
const controller = new AbortController();
|
|
423
|
+
let request;
|
|
424
|
+
const observed = {};
|
|
425
|
+
const pending = (async () => {
|
|
426
|
+
try {
|
|
427
|
+
request = originalCall.call(transport, method, requestParams, timeout, trace, background);
|
|
428
|
+
const result = await request;
|
|
429
|
+
if (span) {
|
|
430
|
+
this.finishRpcResponse(span, result);
|
|
431
|
+
if (generation === this.observationGeneration && (this.isObservedPull(method) || this.pullBarrier?.method === method)) {
|
|
432
|
+
await this.waitForPullBarrier(method, controller.signal, timeout ?? transport._timeout ?? 35_000, span, observed.owner);
|
|
433
|
+
}
|
|
434
|
+
if (generation === this.observationGeneration)
|
|
435
|
+
this.injectRPCFailure(method, span);
|
|
436
|
+
span.success = true;
|
|
437
|
+
}
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
if (span) {
|
|
442
|
+
span.success = false;
|
|
443
|
+
span.error = error instanceof Error ? error.name : String(error);
|
|
444
|
+
}
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
if (span)
|
|
449
|
+
span.end_ms = Date.now();
|
|
450
|
+
}
|
|
451
|
+
})();
|
|
452
|
+
pending.cancel = () => { controller.abort(); request?.cancel?.(); };
|
|
453
|
+
this.rpcObservedPulls.set(pending, observed);
|
|
454
|
+
return pending;
|
|
213
455
|
};
|
|
214
456
|
}
|
|
457
|
+
storeEventLedger(event, value) {
|
|
458
|
+
const data = jsonObjectOf(value) ?? {};
|
|
459
|
+
const internal = this.client;
|
|
460
|
+
const payload = JSON.parse(stableStringify(jsonSafe(data.payload ?? null)));
|
|
461
|
+
this.eventLedger.push({
|
|
462
|
+
observation_id: this.rawPushObservationId, event, event_index: this.eventLedger.length + 1, time_ms: Date.now(),
|
|
463
|
+
scope: event === 'message.received' ? 'p2p' : 'group', receiver_aid: this.aid,
|
|
464
|
+
device_id: textOf(internal._deviceId), slot_id: textOf(internal._slotId ?? this.slotId),
|
|
465
|
+
sender_aid: textOf(data.sender_aid ?? data.from_aid ?? data.from), group_id: textOf(data.group_id),
|
|
466
|
+
message_id: textOf(data.message_id ?? data.id), seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
|
|
467
|
+
storage: textOf(data.storage), volatile_cursor: textOf(data.volatile_cursor),
|
|
468
|
+
payload, payload_sha256: sha256Json(payload), need_render: data.need_render !== false,
|
|
469
|
+
...(Object.prototype.hasOwnProperty.call(data, 'mention_mode') ? { mention_mode: data.mention_mode } : {}),
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
isObservedPull(method) {
|
|
473
|
+
return ['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.pull_messages', 'group.pull_events', 'group.events'].includes(method);
|
|
474
|
+
}
|
|
475
|
+
startRpcSpan(method, value) {
|
|
476
|
+
if (!this.isObservedPull(method) && !['message.peers', 'message.ack', 'message.v2.ack', 'group.ack', 'group.v2.ack', 'group.ack_messages', 'group.ack_events'].includes(method))
|
|
477
|
+
return null;
|
|
478
|
+
const params = jsonObjectOf(value) ?? {};
|
|
479
|
+
const senderAid = textOf(params.sender_aid), groupId = textOf(params.group_id ?? params.group_aid);
|
|
480
|
+
const namespace = method === 'message.peers' ? '' : method.startsWith('group.')
|
|
481
|
+
? groupId ? `${method === 'group.pull_events' || method === 'group.events' || method === 'group.ack_events' ? 'group_event' : 'group'}:${groupId}` : ''
|
|
482
|
+
: senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
|
|
483
|
+
const span = {
|
|
484
|
+
observation_id: this.rawPushObservationId, span_id: crypto.randomUUID(), boundary: 'transport_call', method,
|
|
485
|
+
start_ms: Date.now(), response_ms: null, end_ms: null, success: null,
|
|
486
|
+
cursor_scope: textOf(params.cursor_scope) || (method === 'message.peers' || senderAid ? 'peer' : method.startsWith('group.') ? namespace.startsWith('group_event:') ? 'group_event' : 'group' : 'global'), sender_aid: senderAid,
|
|
487
|
+
group_id: groupId, namespace, request_window: this.observedRequestWindow(params),
|
|
488
|
+
response_message_count: 0, response_volatile_count: 0, response_event_count: 0,
|
|
489
|
+
response_item_keys: [], response_event_keys: [],
|
|
490
|
+
has_more: null, persistent_has_more: null, volatile_has_more: null, has_more_persistent: null, has_more_volatile: null, effective_ack_seq: null,
|
|
491
|
+
};
|
|
492
|
+
this.rpcSpans.push(span);
|
|
493
|
+
return span;
|
|
494
|
+
}
|
|
495
|
+
observedRequestWindow(params) {
|
|
496
|
+
const window = {};
|
|
497
|
+
for (const key of ['after_seq', 'before_seq', 'until_seq', 'after_message_seq', 'after_event_seq', 'up_to_seq', 'seq', 'ack_seq', 'msg_seq', 'event_seq', 'limit', 'max_pages']) {
|
|
498
|
+
if (typeof params[key] === 'number' && Number.isSafeInteger(params[key]))
|
|
499
|
+
window[key] = params[key];
|
|
500
|
+
}
|
|
501
|
+
for (const key of ['window_mode', 'cursor_scope', 'sender_aid', 'group_id', 'group_aid', 'device_id', 'slot_id', 'after_volatile_cursor', 'mode', 'after_sender_aid']) {
|
|
502
|
+
if (typeof params[key] === 'string')
|
|
503
|
+
window[key] = params[key];
|
|
504
|
+
}
|
|
505
|
+
for (const key of ['force', 'pending_only'])
|
|
506
|
+
if (typeof params[key] === 'boolean')
|
|
507
|
+
window[key] = params[key];
|
|
508
|
+
for (const key of ['volatile_cursors', 'volatile_ids']) {
|
|
509
|
+
if (Array.isArray(params[key]))
|
|
510
|
+
window[key] = params[key].filter((item) => typeof item === 'string');
|
|
511
|
+
}
|
|
512
|
+
return window;
|
|
513
|
+
}
|
|
514
|
+
finishRpcResponse(span, value) {
|
|
515
|
+
const response = jsonObjectOf(value) ?? {};
|
|
516
|
+
const messages = Array.isArray(response.messages) ? response.messages : [];
|
|
517
|
+
span.response_ms = Date.now();
|
|
518
|
+
span.response_message_count = messages.length;
|
|
519
|
+
span.response_volatile_count = messages.filter((item) => jsonObjectOf(item)?.storage === 'volatile').length;
|
|
520
|
+
span.response_event_count = Array.isArray(response.events) ? response.events.length : 0;
|
|
521
|
+
const sequenceOf = (raw, ...keys) => {
|
|
522
|
+
for (const key of keys) {
|
|
523
|
+
const value = raw[key];
|
|
524
|
+
if (typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value))
|
|
525
|
+
return value;
|
|
526
|
+
}
|
|
527
|
+
return 0;
|
|
528
|
+
};
|
|
529
|
+
const itemKeys = new Map();
|
|
530
|
+
for (const key of ['messages', 'volatile_messages', 'volatile']) {
|
|
531
|
+
for (const item of Array.isArray(response[key]) ? response[key] : []) {
|
|
532
|
+
const raw = jsonObjectOf(item) ?? {}, cursor = textOf(raw.volatile_cursor);
|
|
533
|
+
const identity = { message_id: textOf(raw.message_id ?? raw.id),
|
|
534
|
+
seq: sequenceOf(raw, 'seq', 'message_seq', 'msg_seq'),
|
|
535
|
+
storage: typeof raw.storage === 'string' ? raw.storage : cursor ? 'volatile' : 'persistent', volatile_cursor: cursor };
|
|
536
|
+
itemKeys.set(JSON.stringify(identity), identity);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
span.response_item_keys = [...itemKeys.values()];
|
|
540
|
+
span.response_event_keys = (Array.isArray(response.events) ? response.events : []).map((item) => {
|
|
541
|
+
const raw = jsonObjectOf(item) ?? {};
|
|
542
|
+
return { event_id: textOf(raw.event_id), event_seq: sequenceOf(raw, 'event_seq', 'seq') };
|
|
543
|
+
});
|
|
544
|
+
span.has_more = typeof response.has_more === 'boolean' ? response.has_more : null;
|
|
545
|
+
span.has_more_persistent = typeof response.persistent_has_more === 'boolean' ? response.persistent_has_more : null;
|
|
546
|
+
span.has_more_volatile = typeof response.volatile_has_more === 'boolean' ? response.volatile_has_more : null;
|
|
547
|
+
span.persistent_has_more = span.has_more_persistent;
|
|
548
|
+
span.volatile_has_more = span.has_more_volatile;
|
|
549
|
+
for (const key of ['peer_ack_seq', 'effective_ack_seq', 'server_ack_seq', 'ack_seq', 'up_to_seq', 'acked_seq']) {
|
|
550
|
+
if (typeof response[key] === 'number' && Number.isSafeInteger(response[key]))
|
|
551
|
+
span[key] = response[key];
|
|
552
|
+
}
|
|
553
|
+
const effective = response.peer_ack_seq ?? response.effective_ack_seq ?? response.ack_seq ?? response.up_to_seq ?? response.acked_seq;
|
|
554
|
+
if (typeof effective === 'number' && Number.isSafeInteger(effective))
|
|
555
|
+
span.effective_ack_seq = effective;
|
|
556
|
+
const volatileAck = jsonObjectOf(response.volatile_ack);
|
|
557
|
+
if (volatileAck) {
|
|
558
|
+
const ack = {};
|
|
559
|
+
for (const key of ['accepted', 'expired'])
|
|
560
|
+
if (typeof volatileAck[key] === 'number' && Number.isSafeInteger(volatileAck[key]))
|
|
561
|
+
ack[key] = volatileAck[key];
|
|
562
|
+
if (typeof volatileAck.storage === 'string')
|
|
563
|
+
ack.storage = volatileAck.storage;
|
|
564
|
+
span.volatile_ack = ack;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
releasePullBarrier() {
|
|
568
|
+
const barrier = this.pullBarrier;
|
|
569
|
+
if (!barrier || barrier.released)
|
|
570
|
+
return;
|
|
571
|
+
barrier.released = true;
|
|
572
|
+
barrier.release();
|
|
573
|
+
}
|
|
574
|
+
injectRPCFailure(method, span) {
|
|
575
|
+
const failure = this.rpcFailures?.find((item) => !item.hit && item.method === method && item.sender_aid === span.sender_aid);
|
|
576
|
+
if (!failure)
|
|
577
|
+
return;
|
|
578
|
+
failure.hit = true;
|
|
579
|
+
span.injected_failure = failure.kind ?? 'error';
|
|
580
|
+
if (failure.kind === 'timeout')
|
|
581
|
+
throw new TimeoutError('cross-sdk 响应后一次超时');
|
|
582
|
+
throw new Error('cross-sdk 响应后一次失败');
|
|
583
|
+
}
|
|
584
|
+
resetObservationLedgers(pullBarrierCount = 0, scope = 'all', controls = {}) {
|
|
585
|
+
this.releasePullBarrier();
|
|
586
|
+
this.observationGeneration = (this.observationGeneration ?? 0) + 1;
|
|
587
|
+
this.eventLedger = [];
|
|
588
|
+
this.batchEvents = [];
|
|
589
|
+
this.singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
|
|
590
|
+
this.rpcSpans = [];
|
|
591
|
+
this.rpcFailures = Array.isArray(controls.rpc_failures) ? controls.rpc_failures.map((item) => ({ ...jsonObjectOf(item) })) : [];
|
|
592
|
+
this.pullBarrier = null;
|
|
593
|
+
if (Number.isSafeInteger(pullBarrierCount) && pullBarrierCount > 0) {
|
|
594
|
+
let release;
|
|
595
|
+
const promise = new Promise((resolve) => { release = resolve; });
|
|
596
|
+
this.pullBarrier = { target: pullBarrierCount, scope: scope === 'p2p' ? 'p2p' : 'all', admitted: 0, waiting: 0, released: false, promise, release,
|
|
597
|
+
sender: textOf(controls.pull_barrier_sender_aid), method: textOf(controls.pull_barrier_method), preserve: controls.preserve_pull_barrier === true };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async waitForPullBarrier(method, signal, timeout, span, owner) {
|
|
601
|
+
const barrier = this.pullBarrier;
|
|
602
|
+
if (!barrier || barrier.released || barrier.admitted >= barrier.target)
|
|
603
|
+
return;
|
|
604
|
+
if (barrier.scope === 'p2p' && method !== 'message.pull' && method !== 'message.v2.pull' && barrier.method !== method)
|
|
605
|
+
return;
|
|
606
|
+
if ((barrier.sender && barrier.sender !== span.sender_aid) || (barrier.method && barrier.method !== method))
|
|
607
|
+
return;
|
|
608
|
+
barrier.admitted += 1;
|
|
609
|
+
barrier.waiting += 1;
|
|
610
|
+
if (barrier.preserve) {
|
|
611
|
+
barrier.heldMethod = method;
|
|
612
|
+
barrier.owner = owner;
|
|
613
|
+
barrier.span = span;
|
|
614
|
+
}
|
|
615
|
+
let timer;
|
|
616
|
+
let abort;
|
|
617
|
+
try {
|
|
618
|
+
await Promise.race([barrier.promise, new Promise((_resolve, reject) => {
|
|
619
|
+
abort = () => reject(new Error('pull observation barrier cancelled'));
|
|
620
|
+
if (!barrier.preserve)
|
|
621
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
622
|
+
if (signal.aborted && !barrier.preserve)
|
|
623
|
+
abort();
|
|
624
|
+
timer = setTimeout(() => reject(new Error('pull observation barrier timeout')), Math.max(1, timeout));
|
|
625
|
+
})]);
|
|
626
|
+
}
|
|
627
|
+
finally {
|
|
628
|
+
if (timer)
|
|
629
|
+
clearTimeout(timer);
|
|
630
|
+
signal.removeEventListener('abort', abort);
|
|
631
|
+
barrier.waiting -= 1;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
215
634
|
storeRawPushObservation(rawEvent, scope, defaultKind, value) {
|
|
216
635
|
const data = jsonObjectOf(value) ?? {};
|
|
217
636
|
const hasInline = Object.prototype.hasOwnProperty.call(data, 'inline_message');
|
|
@@ -299,6 +718,8 @@ export class CrossSdkTsAgent {
|
|
|
299
718
|
};
|
|
300
719
|
}
|
|
301
720
|
async close() {
|
|
721
|
+
this.resetObservationLedgers();
|
|
722
|
+
this.retireP2pDelivery(this.client);
|
|
302
723
|
try {
|
|
303
724
|
await this.client.close();
|
|
304
725
|
}
|
|
@@ -310,7 +731,7 @@ export class CrossSdkTsAgent {
|
|
|
310
731
|
const internal = this.client;
|
|
311
732
|
const store = new AIDStore({
|
|
312
733
|
aunPath: this.aunPath,
|
|
313
|
-
encryptionSeed: '',
|
|
734
|
+
encryptionSeed: process.env.AUN_TEST_ENCRYPTION_SEED || process.env.AUN_ENCRYPTION_SEED || '',
|
|
314
735
|
slotId: this.slotId,
|
|
315
736
|
debug: this.debug,
|
|
316
737
|
});
|
|
@@ -343,6 +764,7 @@ export class CrossSdkTsAgent {
|
|
|
343
764
|
throw new Error(`load identity failed: ${loaded.error.code}: ${loaded.error.message}`);
|
|
344
765
|
}
|
|
345
766
|
this.client.loadIdentity(loaded.data.aid);
|
|
767
|
+
this.captureCredentialStartup();
|
|
346
768
|
const checked = await store.checkAgentMd(this.aid);
|
|
347
769
|
const localEtag = checked.ok ? String(checked.data.local_etag ?? '').trim() : '';
|
|
348
770
|
const remoteEtag = checked.ok ? String(checked.data.remote_etag ?? '').trim() : '';
|
|
@@ -489,6 +911,7 @@ export class CrossSdkTsAgent {
|
|
|
489
911
|
decrypted,
|
|
490
912
|
encrypted: Boolean(data.e2ee ?? data.encrypted),
|
|
491
913
|
seq: Number(data.seq ?? data.message_seq ?? 0) || 0,
|
|
914
|
+
need_render: data.need_render !== false,
|
|
492
915
|
ack_seq: Number(data.ack_seq ?? 0) || 0,
|
|
493
916
|
storage: textOf(data.storage),
|
|
494
917
|
volatile_cursor: textOf(data.volatile_cursor),
|
|
@@ -514,6 +937,7 @@ export class CrossSdkTsAgent {
|
|
|
514
937
|
decrypted,
|
|
515
938
|
encrypted: Boolean(data.e2ee ?? data.encrypted),
|
|
516
939
|
seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
|
|
940
|
+
need_render: data.need_render !== false,
|
|
517
941
|
error_code: errorCode,
|
|
518
942
|
raw_sha256: sha256Json(data),
|
|
519
943
|
...metadata,
|
|
@@ -543,6 +967,7 @@ export class CrossSdkTsAgent {
|
|
|
543
967
|
}
|
|
544
968
|
if (req.method === 'POST' && url.pathname === '/reset') {
|
|
545
969
|
const body = await readJson(req);
|
|
970
|
+
this.resetObservationLedgers();
|
|
546
971
|
const traceId = textOf(body.trace_id ?? '');
|
|
547
972
|
if (traceId) {
|
|
548
973
|
this.inbox = this.inbox.filter((item) => item.trace_id !== traceId);
|
|
@@ -553,6 +978,7 @@ export class CrossSdkTsAgent {
|
|
|
553
978
|
else {
|
|
554
979
|
this.inbox = [];
|
|
555
980
|
this.groupInbox = [];
|
|
981
|
+
this.peersSnapshots = [];
|
|
556
982
|
this.traces.clear();
|
|
557
983
|
this.sendResults.clear();
|
|
558
984
|
}
|
|
@@ -561,11 +987,38 @@ export class CrossSdkTsAgent {
|
|
|
561
987
|
}
|
|
562
988
|
if (req.method === 'POST' && url.pathname === '/test/raw-push/reset') {
|
|
563
989
|
const body = await readJson(req);
|
|
990
|
+
if ((body.pull_barrier_count !== undefined && (typeof body.pull_barrier_count !== 'number' || !Number.isSafeInteger(body.pull_barrier_count) || body.pull_barrier_count < 0)) ||
|
|
991
|
+
(body.pull_barrier_scope !== undefined && !['all', 'p2p'].includes(body.pull_barrier_scope)) ||
|
|
992
|
+
(body.observation_id !== undefined && typeof body.observation_id !== 'string') ||
|
|
993
|
+
(body.release_pull_barrier !== undefined && typeof body.release_pull_barrier !== 'boolean') ||
|
|
994
|
+
['p2p_subscription', 'p2p_delivery_paused', 'preserve_observations'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
|
|
995
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid observation parameters' });
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (typeof body.p2p_subscription === 'boolean')
|
|
999
|
+
this.setP2pSubscription(body.p2p_subscription);
|
|
1000
|
+
const p2pPullSpanIds = body.preserve_observations === true && body.p2p_subscription === false
|
|
1001
|
+
? this.rpcSpans.filter((span) => span.method === 'message.pull' || span.method === 'message.v2.pull').map((span) => textOf(span.span_id)) : undefined;
|
|
1002
|
+
if (typeof body.p2p_delivery_paused === 'boolean')
|
|
1003
|
+
this.setP2pDeliveryPaused(body.p2p_delivery_paused);
|
|
1004
|
+
if (body.preserve_observations === true) {
|
|
1005
|
+
if (body.release_pull_barrier === true)
|
|
1006
|
+
this.releasePullBarrier();
|
|
1007
|
+
sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId, ...(p2pPullSpanIds !== undefined ? { p2p_pull_span_ids: p2pPullSpanIds } : {}) });
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (body.release_pull_barrier === true) {
|
|
1011
|
+
this.releasePullBarrier();
|
|
1012
|
+
sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
this.resetObservationLedgers(body.pull_barrier_count, textOf(body.pull_barrier_scope), body);
|
|
564
1016
|
this.rawPushObservations = [];
|
|
565
1017
|
this.rawPushRpcMethods = [];
|
|
566
1018
|
this.groupChangedEvents = [];
|
|
567
1019
|
this.groupInviteEvents = [];
|
|
568
1020
|
this.readStateEvents = [];
|
|
1021
|
+
this.peersSnapshots = [];
|
|
569
1022
|
this.rawPushObservationId = textOf(body.observation_id ?? '');
|
|
570
1023
|
sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
|
|
571
1024
|
return;
|
|
@@ -584,6 +1037,23 @@ export class CrossSdkTsAgent {
|
|
|
584
1037
|
let rpcEntries = [...this.rawPushRpcMethods];
|
|
585
1038
|
let groupChangedEvents = [...this.groupChangedEvents];
|
|
586
1039
|
let groupInviteEvents = [...this.groupInviteEvents];
|
|
1040
|
+
let eventLedger = [...this.eventLedger ?? []];
|
|
1041
|
+
let rpcSpans = [...this.rpcSpans ?? []];
|
|
1042
|
+
let batchEvents = [...this.batchEvents ?? []];
|
|
1043
|
+
if (observationId)
|
|
1044
|
+
batchEvents = batchEvents.filter((item) => textOf(item.observation_id) === observationId);
|
|
1045
|
+
if (observationId)
|
|
1046
|
+
eventLedger = eventLedger.filter((item) => textOf(item.observation_id) === observationId);
|
|
1047
|
+
if (observationId)
|
|
1048
|
+
rpcSpans = rpcSpans.filter((item) => textOf(item.observation_id) === observationId);
|
|
1049
|
+
if (filters.scope)
|
|
1050
|
+
eventLedger = eventLedger.filter((item) => textOf(item.scope) === filters.scope);
|
|
1051
|
+
if (filters.scope)
|
|
1052
|
+
rpcSpans = rpcSpans.filter((item) => textOf(item.method).startsWith(filters.scope === 'p2p' ? 'message.' : 'group.'));
|
|
1053
|
+
if (filters.group_id)
|
|
1054
|
+
eventLedger = eventLedger.filter((item) => textOf(item.group_id) === filters.group_id);
|
|
1055
|
+
if (filters.group_id)
|
|
1056
|
+
rpcSpans = rpcSpans.filter((item) => textOf(item.group_id) === filters.group_id);
|
|
587
1057
|
if (observationId)
|
|
588
1058
|
rpcEntries = rpcEntries.filter((item) => textOf(item.observation_id) === observationId);
|
|
589
1059
|
if (observationId)
|
|
@@ -600,11 +1070,20 @@ export class CrossSdkTsAgent {
|
|
|
600
1070
|
for (const method of rpcMethods)
|
|
601
1071
|
rpcMethodCounts[method] = Number(rpcMethodCounts[method] ?? 0) + 1;
|
|
602
1072
|
sendJson(res, 200, {
|
|
603
|
-
ok: true, items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
|
|
1073
|
+
ok: true, time_ms: Date.now(), items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
|
|
1074
|
+
observation_id: this.rawPushObservationId,
|
|
604
1075
|
rpc_calls: rpcEntries,
|
|
605
1076
|
group_changed_events: groupChangedEvents.slice(-limit),
|
|
606
1077
|
group_invite_events: groupInviteEvents.slice(-limit),
|
|
607
1078
|
read_state_events: this.readStateEvents.slice(-limit),
|
|
1079
|
+
peers_snapshots: this.peersSnapshots.slice(-limit),
|
|
1080
|
+
event_ledger: eventLedger,
|
|
1081
|
+
rpc_spans: rpcSpans,
|
|
1082
|
+
batch_event_mode: this.batchEventMode ?? false,
|
|
1083
|
+
batch_events: batchEvents,
|
|
1084
|
+
single_event_counts: this.singleEventCounts ?? { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 },
|
|
1085
|
+
application_delivery: this.applicationDeliveryState(),
|
|
1086
|
+
pull_barrier: { target: this.pullBarrier?.target ?? 0, waiting: this.pullBarrier?.waiting ?? 0, released: this.pullBarrier?.released ?? true, scope: this.pullBarrier?.scope ?? 'all' },
|
|
608
1087
|
});
|
|
609
1088
|
return;
|
|
610
1089
|
}
|
|
@@ -896,6 +1375,8 @@ export class CrossSdkTsAgent {
|
|
|
896
1375
|
};
|
|
897
1376
|
if (body.ttl !== undefined)
|
|
898
1377
|
params.ttl = Number(body.ttl);
|
|
1378
|
+
if (body.need_render !== undefined)
|
|
1379
|
+
params.need_render = body.need_render;
|
|
899
1380
|
try {
|
|
900
1381
|
const result = await this.client.call('message.send', params);
|
|
901
1382
|
const resultObj = result && typeof result === 'object' ? result : {};
|
|
@@ -931,6 +1412,10 @@ export class CrossSdkTsAgent {
|
|
|
931
1412
|
const params = {};
|
|
932
1413
|
if (seq > 0)
|
|
933
1414
|
params.seq = seq;
|
|
1415
|
+
if (textOf(body.sender_aid).trim())
|
|
1416
|
+
params.sender_aid = textOf(body.sender_aid).trim();
|
|
1417
|
+
if (body.cursor_scope !== undefined)
|
|
1418
|
+
params.cursor_scope = body.cursor_scope;
|
|
934
1419
|
if (Array.isArray(body.volatile_cursors))
|
|
935
1420
|
params.volatile_cursors = body.volatile_cursors;
|
|
936
1421
|
else if (textOf(body.volatile_cursor))
|
|
@@ -952,9 +1437,26 @@ export class CrossSdkTsAgent {
|
|
|
952
1437
|
}
|
|
953
1438
|
async pull(req, res) {
|
|
954
1439
|
const body = await readJson(req);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1440
|
+
if ((body.limit !== undefined && (typeof body.limit !== 'number' || !Number.isSafeInteger(body.limit) || body.limit < 1 || body.limit > 200)) ||
|
|
1441
|
+
(body.max_pages !== undefined && (typeof body.max_pages !== 'number' || !Number.isSafeInteger(body.max_pages) || body.max_pages < 1)) ||
|
|
1442
|
+
(body.after_seq !== undefined && (typeof body.after_seq !== 'number' || !Number.isSafeInteger(body.after_seq) || body.after_seq < 0)) ||
|
|
1443
|
+
['sender_aid', 'cursor_scope', 'after_volatile_cursor'].some((key) => body[key] !== undefined && typeof body[key] !== 'string') ||
|
|
1444
|
+
(body.force !== undefined && typeof body.force !== 'boolean') ||
|
|
1445
|
+
(body.window_mode !== undefined && !['forward', 'tail'].includes(body.window_mode))) {
|
|
1446
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid pull parameters' });
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
const senderAid = textOf(body.sender_aid).trim();
|
|
1450
|
+
const limit = body.limit ?? 50;
|
|
1451
|
+
const params = { limit };
|
|
1452
|
+
if (senderAid)
|
|
1453
|
+
params.sender_aid = senderAid;
|
|
1454
|
+
if (body.after_seq !== undefined)
|
|
1455
|
+
params.after_seq = body.after_seq;
|
|
1456
|
+
for (const key of ['cursor_scope', 'force', 'window_mode', 'max_pages']) {
|
|
1457
|
+
if (body[key] !== undefined)
|
|
1458
|
+
params[key] = body[key];
|
|
1459
|
+
}
|
|
958
1460
|
if (textOf(body.after_volatile_cursor))
|
|
959
1461
|
params.after_volatile_cursor = textOf(body.after_volatile_cursor);
|
|
960
1462
|
try {
|
|
@@ -976,6 +1478,20 @@ export class CrossSdkTsAgent {
|
|
|
976
1478
|
}
|
|
977
1479
|
async messageCall(req, res) {
|
|
978
1480
|
const body = await readJson(req);
|
|
1481
|
+
if (['start', 'status', 'detach'].includes(textOf(body.action))) {
|
|
1482
|
+
this.messageOperations ??= new Map();
|
|
1483
|
+
const id = textOf(body.operation_id);
|
|
1484
|
+
if (body.action === 'start') {
|
|
1485
|
+
const operation = { ok: true, operation_id: id, status: 'running', started_ms: Date.now() };
|
|
1486
|
+
this.messageOperations.set(id, operation);
|
|
1487
|
+
Promise.resolve().then(() => { operation.entered_ms = Date.now(); return this.client.call(textOf(body.method), (jsonObjectOf(body.params) ?? {})); }).then((result) => Object.assign(operation, { status: 'completed', end_ms: Date.now(), result: jsonSafe(result) }), (error) => Object.assign(operation, { status: 'failed', end_ms: Date.now(), error_code: error instanceof Error ? error.name : 'Error', error_message: error instanceof Error ? error.message : String(error) }));
|
|
1488
|
+
}
|
|
1489
|
+
const operation = this.messageOperations.get(id);
|
|
1490
|
+
if (body.action === 'detach')
|
|
1491
|
+
Object.assign(operation, { detached: true, cancellation: 'application_wait_only', detached_ms: Date.now() });
|
|
1492
|
+
sendJson(res, 200, operation);
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
979
1495
|
const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
|
|
980
1496
|
const method = textOf(body.method).trim();
|
|
981
1497
|
const params = jsonObjectOf(body.params) ?? {};
|
|
@@ -1007,15 +1523,15 @@ export class CrossSdkTsAgent {
|
|
|
1007
1523
|
sendJson(res, 500, error);
|
|
1008
1524
|
}
|
|
1009
1525
|
}
|
|
1010
|
-
syncNamespace(scope, groupId = '') {
|
|
1526
|
+
syncNamespace(scope, groupId = '', senderAid = '') {
|
|
1011
1527
|
if (scope === 'p2p')
|
|
1012
|
-
return `p2p:${this.aid}`;
|
|
1528
|
+
return senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
|
|
1013
1529
|
if (scope === 'group' && groupId)
|
|
1014
1530
|
return `group:${groupId}`;
|
|
1015
1531
|
throw new Error('scope must be p2p, or group with group_id');
|
|
1016
1532
|
}
|
|
1017
|
-
syncStateValue(scope, groupId = '') {
|
|
1018
|
-
const namespace = this.syncNamespace(scope, groupId);
|
|
1533
|
+
syncStateValue(scope, groupId = '', senderAid = '') {
|
|
1534
|
+
const namespace = this.syncNamespace(scope, groupId, senderAid);
|
|
1019
1535
|
const tracker = this.client._seqTracker;
|
|
1020
1536
|
if (!tracker)
|
|
1021
1537
|
throw new Error('SeqTracker is unavailable');
|
|
@@ -1024,9 +1540,10 @@ export class CrossSdkTsAgent {
|
|
|
1024
1540
|
syncState(url, res) {
|
|
1025
1541
|
const scope = textOf(url.searchParams.get('scope') || 'p2p').trim().toLowerCase();
|
|
1026
1542
|
const groupId = textOf(url.searchParams.get('group_id') || '').trim();
|
|
1543
|
+
const senderAid = textOf(url.searchParams.get('sender_aid') || '').trim();
|
|
1027
1544
|
try {
|
|
1028
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
1029
|
-
sendJson(res, 200, { ok: true, scope, ...snapshot });
|
|
1545
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
1546
|
+
sendJson(res, 200, { ok: true, scope, ...snapshot, peers_snapshots: this.peersSnapshots });
|
|
1030
1547
|
}
|
|
1031
1548
|
catch (err) {
|
|
1032
1549
|
sendJson(res, 400, {
|
|
@@ -1046,27 +1563,24 @@ export class CrossSdkTsAgent {
|
|
|
1046
1563
|
const body = await readJson(req);
|
|
1047
1564
|
const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
|
|
1048
1565
|
const groupId = textOf(body.group_id || '').trim();
|
|
1566
|
+
const senderAid = textOf(body.sender_aid || '').trim();
|
|
1049
1567
|
const afterSeq = Number(body.after_seq ?? 0) || 0;
|
|
1050
1568
|
const limit = Number(body.limit ?? 50) || 50;
|
|
1051
1569
|
try {
|
|
1052
|
-
const params = {
|
|
1053
|
-
if (
|
|
1054
|
-
params.
|
|
1570
|
+
const params = { limit, window_mode: tail ? 'tail' : 'forward' };
|
|
1571
|
+
if (scope === 'group' || body.after_seq !== undefined)
|
|
1572
|
+
params.after_seq = afterSeq;
|
|
1573
|
+
if (scope === 'p2p' && senderAid)
|
|
1574
|
+
params.sender_aid = senderAid;
|
|
1575
|
+
for (const key of ['cursor_scope', 'force', 'max_pages', 'after_volatile_cursor']) {
|
|
1576
|
+
if (body[key] !== undefined)
|
|
1577
|
+
params[key] = body[key];
|
|
1578
|
+
}
|
|
1055
1579
|
const method = scope === 'group' ? 'group.pull' : 'message.pull';
|
|
1056
1580
|
if (scope === 'group')
|
|
1057
1581
|
params.group_id = groupId;
|
|
1058
|
-
this.syncNamespace(scope, groupId);
|
|
1059
|
-
|
|
1060
|
-
if (tail) {
|
|
1061
|
-
result = await this.client.call(method, params);
|
|
1062
|
-
}
|
|
1063
|
-
else {
|
|
1064
|
-
const transport = this.client._transport;
|
|
1065
|
-
if (!transport?.call)
|
|
1066
|
-
throw new Error('raw transport is unavailable');
|
|
1067
|
-
const wireMethod = scope === 'group' ? 'group.v2.pull' : 'message.v2.pull';
|
|
1068
|
-
result = await transport.call(wireMethod, params);
|
|
1069
|
-
}
|
|
1582
|
+
this.syncNamespace(scope, groupId, senderAid);
|
|
1583
|
+
const result = await this.client.call(method, params);
|
|
1070
1584
|
const resultObj = jsonObjectOf(result) ?? {};
|
|
1071
1585
|
const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
|
|
1072
1586
|
if (tail) {
|
|
@@ -1077,7 +1591,7 @@ export class CrossSdkTsAgent {
|
|
|
1077
1591
|
await this.storeInboxItem(this.normalizeMessage(msg, true));
|
|
1078
1592
|
}
|
|
1079
1593
|
}
|
|
1080
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
1594
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
1081
1595
|
sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
|
|
1082
1596
|
}
|
|
1083
1597
|
catch (err) {
|
|
@@ -1093,6 +1607,7 @@ export class CrossSdkTsAgent {
|
|
|
1093
1607
|
const body = await readJson(req);
|
|
1094
1608
|
const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
|
|
1095
1609
|
const groupId = textOf(body.group_id || '').trim();
|
|
1610
|
+
const senderAid = textOf(body.sender_aid || '').trim();
|
|
1096
1611
|
const beforeSeq = Number(body.before_seq ?? 0) || 0;
|
|
1097
1612
|
const limit = Number(body.limit ?? 50) || 50;
|
|
1098
1613
|
try {
|
|
@@ -1100,9 +1615,13 @@ export class CrossSdkTsAgent {
|
|
|
1100
1615
|
const method = scope === 'group' ? 'group.history' : 'message.history';
|
|
1101
1616
|
if (scope === 'group')
|
|
1102
1617
|
params.group_id = groupId;
|
|
1103
|
-
|
|
1618
|
+
if (scope === 'p2p' && senderAid)
|
|
1619
|
+
params.sender_aid = senderAid;
|
|
1620
|
+
if (body.cursor_scope !== undefined)
|
|
1621
|
+
params.cursor_scope = body.cursor_scope;
|
|
1622
|
+
this.syncNamespace(scope, groupId, senderAid);
|
|
1104
1623
|
const result = await this.client.call(method, params);
|
|
1105
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
1624
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
1106
1625
|
sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
|
|
1107
1626
|
}
|
|
1108
1627
|
catch (err) {
|
|
@@ -1117,7 +1636,25 @@ export class CrossSdkTsAgent {
|
|
|
1117
1636
|
async sessionDisconnect(res) {
|
|
1118
1637
|
try {
|
|
1119
1638
|
this.ready = false;
|
|
1120
|
-
|
|
1639
|
+
const barrier = this.pullBarrier;
|
|
1640
|
+
let restore;
|
|
1641
|
+
if (barrier?.preserve && barrier.waiting === 1 && !barrier.released && ['message.pull', 'message.v2.pull'].includes(barrier.heldMethod ?? '')) {
|
|
1642
|
+
const binding = barrier.owner, pipeline = this.client._rpcPipeline;
|
|
1643
|
+
const owners = [...(pipeline?.pullGateStates.values() ?? [])].flatMap((state) => [...state.activeByNamespace.values()])
|
|
1644
|
+
.filter((job) => job === binding?.job && job.key === binding.key && job.namespace === binding.namespace && job.running && !job.timedOut && !job.invalidated && job.cancel === binding.cancel);
|
|
1645
|
+
if (owners.length !== 1)
|
|
1646
|
+
throw new Error('保留响应无法定位唯一受跟踪的 pull owner');
|
|
1647
|
+
const owner = owners[0], settled = owner.settled;
|
|
1648
|
+
owner.settled = Promise.resolve();
|
|
1649
|
+
restore = () => { owner.settled = settled; };
|
|
1650
|
+
barrier.span.barrier_owner_isolated = true;
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
await this.client.disconnect();
|
|
1654
|
+
}
|
|
1655
|
+
finally {
|
|
1656
|
+
restore?.();
|
|
1657
|
+
}
|
|
1121
1658
|
sendJson(res, 200, { ok: true, state: textOf(this.client.state) });
|
|
1122
1659
|
}
|
|
1123
1660
|
catch (err) {
|
|
@@ -1130,14 +1667,19 @@ export class CrossSdkTsAgent {
|
|
|
1130
1667
|
}
|
|
1131
1668
|
async sessionConnect(req, res) {
|
|
1132
1669
|
const body = await readJson(req);
|
|
1670
|
+
if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
|
|
1671
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1133
1674
|
const backgroundSync = body.background_sync === true;
|
|
1134
1675
|
if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
|
|
1135
1676
|
this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
|
|
1136
1677
|
}
|
|
1137
1678
|
try {
|
|
1679
|
+
this.setBatchEventMode(typeof body.batch_events === 'boolean' ? body.batch_events : this.batchEventMode ?? false);
|
|
1138
1680
|
await this.client.connect({ auto_reconnect: false, background_sync: backgroundSync });
|
|
1139
1681
|
this.ready = true;
|
|
1140
|
-
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
|
|
1682
|
+
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
|
|
1141
1683
|
}
|
|
1142
1684
|
catch (err) {
|
|
1143
1685
|
sendJson(res, 500, {
|
|
@@ -1149,19 +1691,26 @@ export class CrossSdkTsAgent {
|
|
|
1149
1691
|
}
|
|
1150
1692
|
async sessionRestart(req, res) {
|
|
1151
1693
|
const body = await readJson(req);
|
|
1694
|
+
if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
|
|
1695
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1152
1698
|
const backgroundSync = body.background_sync === true;
|
|
1153
1699
|
if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
|
|
1154
1700
|
this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
|
|
1155
1701
|
}
|
|
1156
1702
|
this.ready = false;
|
|
1157
1703
|
try {
|
|
1704
|
+
if (typeof body.batch_events === 'boolean')
|
|
1705
|
+
this.batchEventMode = body.batch_events;
|
|
1706
|
+
this.retireP2pDelivery(this.client);
|
|
1158
1707
|
await this.client.close();
|
|
1159
1708
|
this.client = this.newClient();
|
|
1160
1709
|
this.bindClientEvents();
|
|
1161
1710
|
await this.ensureConnected(backgroundSync);
|
|
1162
1711
|
this.startupError = '';
|
|
1163
1712
|
this.ready = true;
|
|
1164
|
-
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
|
|
1713
|
+
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
|
|
1165
1714
|
}
|
|
1166
1715
|
catch (err) {
|
|
1167
1716
|
this.startupError = `${err instanceof Error ? err.name : 'Error'}: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -1231,15 +1780,36 @@ export class CrossSdkTsAgent {
|
|
|
1231
1780
|
const now = Math.floor(Date.now() / 1000);
|
|
1232
1781
|
if (op === 'request')
|
|
1233
1782
|
return buildRequestBinding(textOf(params.method), textOf(params.target), params.params, textOf(params.credential_cid), textOf(params.nonce), textOf(params.request_id), now, now + 300);
|
|
1234
|
-
if (
|
|
1783
|
+
if (op === 'revocation_checkpoint') {
|
|
1784
|
+
const internal = this.client;
|
|
1785
|
+
if (params.sync === true)
|
|
1786
|
+
await internal._getCredentialRenewal()._syncRevocations();
|
|
1787
|
+
return internal._tokenStore._loadMetadataStrict(this.aid)?.credential_revocations_v1 ?? { version: 1, authorities: {} };
|
|
1788
|
+
}
|
|
1789
|
+
if (op === 'credential_process')
|
|
1790
|
+
return params.startup === true ? this.credentialStartup : this.credentialProcessCheckpoint();
|
|
1791
|
+
if (op === 'manage_credential') {
|
|
1792
|
+
const manager = this.client._getCredentialRenewal();
|
|
1793
|
+
await manager.serial(() => manager.rememberNow(params.credential, {}, this.aid, undefined, true));
|
|
1794
|
+
return await this.client.credential.current(textOf(params.credential.cid));
|
|
1795
|
+
}
|
|
1796
|
+
if (op === 'current_credential')
|
|
1797
|
+
return await this.client.credential.current(textOf(params.credential_cid));
|
|
1798
|
+
if (['register', 'sign', 'certificate', 'local_certificate', 'create', 'prepare', 'transfer_accept', 'renew'].includes(op)) {
|
|
1235
1799
|
const store = this.aidStore();
|
|
1236
1800
|
try {
|
|
1237
|
-
if (op === '
|
|
1801
|
+
if (op === 'transfer_accept')
|
|
1802
|
+
return await this.client.org.lifecycle.transferAccept({ ...jsonObjectOf(params.params), aidStore: store });
|
|
1803
|
+
if (op === 'prepare' || op === 'renew') {
|
|
1238
1804
|
const request = jsonObjectOf(params.params) ?? {};
|
|
1239
1805
|
const orgId = textOf(params.org_id ?? request.org_id);
|
|
1240
1806
|
const loaded = orgId ? store.load(orgId) : null;
|
|
1241
1807
|
const orgSigner = loaded?.ok && loaded.data.aid.isPrivateKeyValid() ? loaded.data.aid : undefined;
|
|
1242
|
-
|
|
1808
|
+
const credentialOptions = jsonObjectOf(params.credential_options);
|
|
1809
|
+
if (op === 'prepare')
|
|
1810
|
+
return this.client.org.prepareRequest(textOf(params.method), request, textOf(params.target), this.client.currentAid, { ...jsonObjectOf(params.options), orgSigner, ...(credentialOptions ? { credentialOptions } : {}) });
|
|
1811
|
+
const prepared = this.client.org.prepareRequest('org.member.renewCredential', request, textOf(params.target) || `org.${orgId.slice(orgId.indexOf('.') + 1)}`, this.client.currentAid, { orgSigner, credentialOptions: jsonObjectOf(params.credential_options) ?? undefined });
|
|
1812
|
+
return { result: await this.client.org.member.renewCredential(prepared), params: prepared };
|
|
1243
1813
|
}
|
|
1244
1814
|
if (op === 'register') {
|
|
1245
1815
|
const registered = await store.register(textOf(params.org_id), { type: 'org', owner: this.aid, exttype: 'company' });
|
|
@@ -1248,12 +1818,14 @@ export class CrossSdkTsAgent {
|
|
|
1248
1818
|
}
|
|
1249
1819
|
const loaded = op === 'certificate'
|
|
1250
1820
|
? await store.resolve(textOf(params.as_aid), { skipAgentMd: true })
|
|
1251
|
-
: (params.as_aid || params.org_id) ? store.load(textOf(params.as_aid ?? params.org_id)) : null;
|
|
1821
|
+
: (params.as_aid || op === 'local_certificate' || params.org_id) ? store.load(textOf(params.as_aid ?? params.org_id)) : null;
|
|
1252
1822
|
if (loaded && !loaded.ok)
|
|
1253
1823
|
throw new Error(String(loaded.error));
|
|
1254
1824
|
const identity = loaded?.data?.aid ?? this.client.currentAid;
|
|
1255
1825
|
const result = { cert_fingerprint: identity.certFingerprint, certificate: identity.certPem };
|
|
1256
|
-
if (op === '
|
|
1826
|
+
if (op === 'local_certificate')
|
|
1827
|
+
result.has_private_key = identity.isPrivateKeyValid();
|
|
1828
|
+
else if (op === 'sign') {
|
|
1257
1829
|
const signed = await identity.sign(Buffer.from(canonicalJson(params.payload)));
|
|
1258
1830
|
if (!signed.ok)
|
|
1259
1831
|
throw new Error(String(signed.error));
|
|
@@ -1281,6 +1853,7 @@ export class CrossSdkTsAgent {
|
|
|
1281
1853
|
const actual = jsonObjectOf(params.context) ?? params;
|
|
1282
1854
|
const memberships = jsonObjectOf(params.memberships) ?? {};
|
|
1283
1855
|
result.verification = { ...this.client.credential.verify(value, { resolveCertificate: resolver, resolveActorCertificate: resolver,
|
|
1856
|
+
resolveStatusProof: (cid) => params.status_proofs?.[cid],
|
|
1284
1857
|
resolveStatus: (cid) => params.statuses?.[cid], now: verificationNow, audience: textOf(actual.audience), subject: textOf(actual.subject) || undefined,
|
|
1285
1858
|
ns: textOf(actual.ns), action: textOf(actual.action), resource: textOf(actual.resource), rootIssuer: textOf(actual.root_issuer) || undefined,
|
|
1286
1859
|
params: jsonObjectOf(actual.params) ?? {}, units: jsonObjectOf(actual.units) ?? {}, clientIp: textOf(actual.client_ip), method: textOf(actual.method), target: textOf(actual.target),
|
|
@@ -1322,6 +1895,18 @@ export class CrossSdkTsAgent {
|
|
|
1322
1895
|
case 'sdk.org.protocol':
|
|
1323
1896
|
result = await this.orgProtocol(params);
|
|
1324
1897
|
break;
|
|
1898
|
+
case 'credential.register':
|
|
1899
|
+
result = await this.client.credential.register(params);
|
|
1900
|
+
break;
|
|
1901
|
+
case 'credential.status':
|
|
1902
|
+
result = await this.client.credential.status(params);
|
|
1903
|
+
break;
|
|
1904
|
+
case 'credential.revoke':
|
|
1905
|
+
result = await this.client.credential.revoke(params);
|
|
1906
|
+
break;
|
|
1907
|
+
case 'credential.revocationList':
|
|
1908
|
+
result = await this.client.credential.revocationList(params);
|
|
1909
|
+
break;
|
|
1325
1910
|
case 'org.lifecycle.create':
|
|
1326
1911
|
result = await this.client.org.lifecycle.create(params);
|
|
1327
1912
|
break;
|
|
@@ -1414,6 +1999,10 @@ export class CrossSdkTsAgent {
|
|
|
1414
1999
|
error_code: err instanceof Error ? err.name : 'Error',
|
|
1415
2000
|
error_message: err instanceof Error ? err.message : String(err),
|
|
1416
2001
|
};
|
|
2002
|
+
if (method.startsWith('org.') || method === 'sdk.org.protocol') {
|
|
2003
|
+
const remote = err;
|
|
2004
|
+
error.rpc_error_code = remote?.stringCode || remote?.data?.error_code || remote?.data?.code || (typeof remote?.code === 'string' ? remote.code : '');
|
|
2005
|
+
}
|
|
1417
2006
|
this.recordTrace(traceId, { stage: 'group_call_error', method, error });
|
|
1418
2007
|
sendJson(res, 500, error);
|
|
1419
2008
|
}
|
|
@@ -1578,6 +2167,8 @@ export class CrossSdkTsAgent {
|
|
|
1578
2167
|
}
|
|
1579
2168
|
}
|
|
1580
2169
|
async callGroupFsAction(action, params, asGroupAid = '') {
|
|
2170
|
+
if (action === 'cache_read')
|
|
2171
|
+
return await this.cacheRead(params, true, asGroupAid);
|
|
1581
2172
|
const groupFs = this.client.group.fs;
|
|
1582
2173
|
let signingStore = null;
|
|
1583
2174
|
const withSigning = (source) => {
|
|
@@ -1717,7 +2308,124 @@ export class CrossSdkTsAgent {
|
|
|
1717
2308
|
}
|
|
1718
2309
|
return response;
|
|
1719
2310
|
}
|
|
2311
|
+
async cacheRead(params, group = false, asGroupAid = '') {
|
|
2312
|
+
const operation = textOf(params.operation || 'read_bytes');
|
|
2313
|
+
if (!['read_bytes', 'stat', 'lstat', 'list', 'ls', 'find', 'du', 'df'].includes(operation)) {
|
|
2314
|
+
throw new Error(`unsupported cache read operation: ${operation}`);
|
|
2315
|
+
}
|
|
2316
|
+
const transport = this.client._transport;
|
|
2317
|
+
const lowlevel = group ? this.client.group.fs.lowlevel : this.client.storage.lowlevel;
|
|
2318
|
+
const counters = { rpc_count: 0, http_count: 0, body_transfers: 0, body_bytes: 0, methods: {}, wire_params: [] };
|
|
2319
|
+
const releaseCall = acquireCacheReadObserver(transport, 'call', (originalCall) => (method, request, ...tail) => {
|
|
2320
|
+
const context = cacheReadContext.getStore();
|
|
2321
|
+
const counted = context && (method.startsWith('storage.') || method.startsWith('group.fs.'));
|
|
2322
|
+
if (counted) {
|
|
2323
|
+
context.rpc_count += 1;
|
|
2324
|
+
context.methods[method] = (context.methods[method] || 0) + 1;
|
|
2325
|
+
context.wire_params.push({ method, params: JSON.parse(JSON.stringify(request ?? {})) });
|
|
2326
|
+
}
|
|
2327
|
+
const pending = originalCall.call(transport, method, request, ...tail);
|
|
2328
|
+
const observed = pending.then((result) => {
|
|
2329
|
+
const payload = jsonObjectOf(result);
|
|
2330
|
+
const content = payload?.content_base64 ?? payload?.content;
|
|
2331
|
+
if (counted && !payload?.not_modified && typeof content === 'string') {
|
|
2332
|
+
context.body_transfers += 1;
|
|
2333
|
+
context.body_bytes += Buffer.from(content, 'base64').byteLength;
|
|
2334
|
+
}
|
|
2335
|
+
return result;
|
|
2336
|
+
});
|
|
2337
|
+
if (pending.cancel)
|
|
2338
|
+
observed.cancel = () => pending.cancel();
|
|
2339
|
+
return observed;
|
|
2340
|
+
});
|
|
2341
|
+
const releaseHttp = acquireCacheReadObserver(lowlevel, 'httpGet', (originalHttp) => async (...args) => {
|
|
2342
|
+
const context = cacheReadContext.getStore();
|
|
2343
|
+
if (context)
|
|
2344
|
+
context.http_count += 1;
|
|
2345
|
+
const data = await originalHttp.call(lowlevel, ...args);
|
|
2346
|
+
if (context) {
|
|
2347
|
+
context.body_transfers += 1;
|
|
2348
|
+
context.body_bytes += data.byteLength;
|
|
2349
|
+
}
|
|
2350
|
+
return data;
|
|
2351
|
+
});
|
|
2352
|
+
const readOnce = async () => {
|
|
2353
|
+
const remotePath = textOf(params.path);
|
|
2354
|
+
const noCache = Boolean(params.no_cache ?? params.noCache);
|
|
2355
|
+
if (group) {
|
|
2356
|
+
const options = withoutKeys(params, ['operation', 'count', 'concurrent', 'no_cache']);
|
|
2357
|
+
options.noCache = noCache;
|
|
2358
|
+
if (operation !== 'read_bytes')
|
|
2359
|
+
return await this.callGroupFsAction(operation === 'list' ? 'ls' : operation, options, asGroupAid);
|
|
2360
|
+
delete options.path;
|
|
2361
|
+
const dst = this.writeTempText('', '.bin');
|
|
2362
|
+
try {
|
|
2363
|
+
const result = await this.callGroupFsAction('cp', { ...options, src: remotePath, dst, force: true }, asGroupAid);
|
|
2364
|
+
const response = jsonObjectOf(result);
|
|
2365
|
+
return { content_base64: response.content_base64, size_bytes: response.size_bytes };
|
|
2366
|
+
}
|
|
2367
|
+
finally {
|
|
2368
|
+
fs.rmSync(dst, { force: true });
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
const storage = this.client.storage;
|
|
2372
|
+
const options = {
|
|
2373
|
+
owner: textOf(params.owner_aid ?? params.ownerAID).trim() || undefined,
|
|
2374
|
+
bucket: textOf(params.bucket).trim() || 'default', noCache,
|
|
2375
|
+
};
|
|
2376
|
+
if (operation !== 'df')
|
|
2377
|
+
options.token = textOf(params.token).trim() || undefined;
|
|
2378
|
+
if (operation === 'read_bytes') {
|
|
2379
|
+
const shareId = textOf(params.share_id ?? params.shareId).trim();
|
|
2380
|
+
if (shareId) {
|
|
2381
|
+
const result = await storage.lowlevel.getByShare({ shareId, noCache });
|
|
2382
|
+
const data = result.download_url ? await storage.lowlevel.httpGet(textOf(result.download_url)) : Buffer.from(textOf(result.content_base64 ?? result.content), 'base64');
|
|
2383
|
+
return { content_base64: Buffer.from(data).toString('base64'), size_bytes: data.byteLength };
|
|
2384
|
+
}
|
|
2385
|
+
for (const key of ['offset', 'limit'])
|
|
2386
|
+
if (params[key] != null)
|
|
2387
|
+
options[key] = Number(params[key]);
|
|
2388
|
+
const data = await storage.readBytes(remotePath, options);
|
|
2389
|
+
return { content_base64: Buffer.from(data).toString('base64'), size_bytes: data.byteLength };
|
|
2390
|
+
}
|
|
2391
|
+
const fields = { list: ['page', 'size', 'marker', 'long', 'recursive'], find: ['name', 'mtime', 'page'], du: [] };
|
|
2392
|
+
for (const key of fields[operation] ?? [])
|
|
2393
|
+
if (params[key] != null)
|
|
2394
|
+
options[key] = params[key];
|
|
2395
|
+
if (params.page_size != null || params.pageSize != null)
|
|
2396
|
+
options.pageSize = Number(params.page_size ?? params.pageSize);
|
|
2397
|
+
if (params.max_depth != null || params.maxDepth != null)
|
|
2398
|
+
options.maxDepth = Number(params.max_depth ?? params.maxDepth);
|
|
2399
|
+
if (params.node_type != null || params.nodeType != null)
|
|
2400
|
+
options.nodeType = params.node_type ?? params.nodeType;
|
|
2401
|
+
if (operation === 'find' && (params.size_expr != null || params.sizeExpr != null))
|
|
2402
|
+
options.size = params.size_expr ?? params.sizeExpr;
|
|
2403
|
+
const method = storage[operation === 'ls' ? 'list' : operation];
|
|
2404
|
+
return await method.call(storage, ...(operation === 'df' ? [options] : [remotePath, options]));
|
|
2405
|
+
};
|
|
2406
|
+
try {
|
|
2407
|
+
const count = Math.max(1, Number(params.count || 1));
|
|
2408
|
+
const values = await cacheReadContext.run(counters, async () => {
|
|
2409
|
+
if (params.concurrent)
|
|
2410
|
+
return await Promise.all(Array.from({ length: count }, () => readOnce()));
|
|
2411
|
+
const items = [];
|
|
2412
|
+
for (let index = 0; index < count; index += 1)
|
|
2413
|
+
items.push(await readOnce());
|
|
2414
|
+
return items;
|
|
2415
|
+
});
|
|
2416
|
+
if (values.some((value) => stableStringify(value) !== stableStringify(values[0])))
|
|
2417
|
+
throw new Error('cache_read returned inconsistent values');
|
|
2418
|
+
const result = operation === 'read_bytes' ? jsonObjectOf(values[0]) : { value: values[0] };
|
|
2419
|
+
return { ...result, ...counters };
|
|
2420
|
+
}
|
|
2421
|
+
finally {
|
|
2422
|
+
releaseCall();
|
|
2423
|
+
releaseHttp();
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
1720
2426
|
async callStorageAction(action, params) {
|
|
2427
|
+
if (action === 'cache_read')
|
|
2428
|
+
return await this.cacheRead(params);
|
|
1721
2429
|
const storage = this.client.storage;
|
|
1722
2430
|
const path = textOf(params.path).trim();
|
|
1723
2431
|
const owner = textOf(params.owner_aid ?? params.ownerAID).trim() || undefined;
|
|
@@ -1896,7 +2604,10 @@ export class CrossSdkTsAgent {
|
|
|
1896
2604
|
case 'revoke_share_link':
|
|
1897
2605
|
return await storage.lowlevel.revokeShareLink({ shareId: textOf(params.share_id ?? params.shareId).trim() });
|
|
1898
2606
|
case 'get_by_share': {
|
|
1899
|
-
const result = await storage.lowlevel.getByShare({
|
|
2607
|
+
const result = await storage.lowlevel.getByShare({
|
|
2608
|
+
shareId: textOf(params.share_id ?? params.shareId).trim(),
|
|
2609
|
+
noCache: Boolean(params.no_cache ?? params.noCache),
|
|
2610
|
+
});
|
|
1900
2611
|
const resultObj = jsonObjectOf(result);
|
|
1901
2612
|
const content = textOf(resultObj?.content);
|
|
1902
2613
|
if (content) {
|
|
@@ -2053,6 +2764,7 @@ export class CrossSdkTsAgent {
|
|
|
2053
2764
|
payload,
|
|
2054
2765
|
encrypt: e2ee,
|
|
2055
2766
|
message_id: messageId,
|
|
2767
|
+
...(body.need_render !== undefined ? { need_render: body.need_render } : {}),
|
|
2056
2768
|
});
|
|
2057
2769
|
const resultObj = result && typeof result === 'object' && !Array.isArray(result) ? result : {};
|
|
2058
2770
|
const response = {
|
|
@@ -2186,7 +2898,7 @@ export class CrossSdkTsAgent {
|
|
|
2186
2898
|
aidStore() {
|
|
2187
2899
|
return new AIDStore({
|
|
2188
2900
|
aunPath: this.aunPath,
|
|
2189
|
-
encryptionSeed: '',
|
|
2901
|
+
encryptionSeed: process.env.AUN_TEST_ENCRYPTION_SEED || process.env.AUN_ENCRYPTION_SEED || '',
|
|
2190
2902
|
slotId: this.slotId,
|
|
2191
2903
|
debug: this.debug,
|
|
2192
2904
|
});
|