@agentunion/fastaun 0.5.20 → 0.5.21
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 +47 -0
- package/_packed_docs/CHANGELOG.md +47 -0
- package/_packed_docs/INDEX.md +453 -440
- package/_packed_docs/KITE_DOCS_GUIDE.md +164 -161
- 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/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/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 +666 -658
- 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/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 +147 -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 +5 -2
- package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -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 +88 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +134 -130
- package/_packed_docs/sdk/INDEX.md +239 -221
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -922
- 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 +106 -43
- 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 +180 -9
- 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 +480 -476
- 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 +1 -1
- 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 +2 -2
- 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 +84 -67
- 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.js +4 -0
- 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 +59 -1
- package/dist/client/delivery.js +1018 -125
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.js +123 -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 +751 -294
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +379 -63
- 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/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 +45 -0
- package/dist/org-protocol.js +754 -11
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +16 -3
- package/dist/ownership-v21.js +35 -17
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +17 -0
- package/dist/ownership-workflow.js +511 -26
- 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/tools/cross-sdk-agent.d.ts +66 -3
- package/dist/tools/cross-sdk-agent.js +466 -36
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +98 -44
- 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
|
@@ -14,6 +14,7 @@ import { publicKeyFingerprint } from '../cert-utils.js';
|
|
|
14
14
|
import { VERSION } from '../version.js';
|
|
15
15
|
import { canonicalJson, digest, buildRequestBinding, verifyMembershipBinding, ProtocolError } from '../org-protocol.js';
|
|
16
16
|
import { ownershipTaskResult } from '../ownership-workflow.js';
|
|
17
|
+
import { TimeoutError } from '../errors.js';
|
|
17
18
|
function envBool(name, fallback = false) {
|
|
18
19
|
const raw = process.env[name];
|
|
19
20
|
if (raw == null)
|
|
@@ -128,10 +129,25 @@ export class CrossSdkTsAgent {
|
|
|
128
129
|
rawPushObservations = [];
|
|
129
130
|
rawPushObservationId = '';
|
|
130
131
|
rawPushRpcMethods = [];
|
|
132
|
+
eventLedger = [];
|
|
133
|
+
batchEventMode = false;
|
|
134
|
+
batchEvents = [];
|
|
135
|
+
singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
|
|
136
|
+
eventBoundClients = new WeakSet();
|
|
137
|
+
batchEventClient;
|
|
138
|
+
batchEventHandlers = [];
|
|
139
|
+
rpcSpans = [];
|
|
140
|
+
observationGeneration = 0;
|
|
141
|
+
pullBarrier = null;
|
|
142
|
+
rpcFailures = [];
|
|
143
|
+
messageOperations = new Map();
|
|
131
144
|
rpcObservedTransports = new WeakSet();
|
|
145
|
+
rpcObservedPipelines = new WeakSet();
|
|
146
|
+
rpcObservedPulls = new WeakMap();
|
|
132
147
|
groupChangedEvents = [];
|
|
133
148
|
groupInviteEvents = [];
|
|
134
149
|
readStateEvents = [];
|
|
150
|
+
peersSnapshots = [];
|
|
135
151
|
ownershipEvents = [];
|
|
136
152
|
traces = new Map();
|
|
137
153
|
sendResults = new Map();
|
|
@@ -154,6 +170,12 @@ export class CrossSdkTsAgent {
|
|
|
154
170
|
return client;
|
|
155
171
|
}
|
|
156
172
|
bindClientEvents() {
|
|
173
|
+
this.eventBoundClients ??= new WeakSet();
|
|
174
|
+
if (this.eventBoundClients.has(this.client)) {
|
|
175
|
+
this.setBatchEventMode(this.batchEventMode ?? false);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.eventBoundClients.add(this.client);
|
|
157
179
|
this.installTransportRpcObserver();
|
|
158
180
|
this.client.on('_raw.message.received', (data) => this.storeRawPushObservation('_raw.message.received', 'p2p', 'legacy', data));
|
|
159
181
|
this.client.on('_raw.peer.v2.message_received', (data) => this.storeRawPushObservation('_raw.peer.v2.message_received', 'p2p', 'v2', data));
|
|
@@ -161,22 +183,30 @@ export class CrossSdkTsAgent {
|
|
|
161
183
|
this.client.on('_raw.group.v2.message_created', (data) => this.storeRawPushObservation('_raw.group.v2.message_created', 'group', 'v2', data));
|
|
162
184
|
this.client.on('_raw.group.changed', (data) => this.storeRawGroupChangedObservation(data));
|
|
163
185
|
this.client.on('message.received', (msg) => {
|
|
164
|
-
|
|
186
|
+
this.countSingleEvent('message.received');
|
|
187
|
+
void this.storeApplicationEvent('message.received', msg);
|
|
165
188
|
});
|
|
166
189
|
this.client.on('message.undecryptable', (msg) => {
|
|
167
190
|
void this.storeInboxItem(this.normalizeMessage(msg, false, 'undecryptable'));
|
|
168
191
|
});
|
|
169
192
|
this.client.on('group.message_created', (msg) => {
|
|
170
|
-
|
|
193
|
+
this.countSingleEvent('group.message_created');
|
|
194
|
+
void this.storeApplicationEvent('group.message_created', msg);
|
|
171
195
|
});
|
|
172
196
|
this.client.on('group.message_undecryptable', (msg) => {
|
|
173
197
|
void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, false, 'undecryptable'));
|
|
174
198
|
});
|
|
175
|
-
this.client.on('group.changed', (data) =>
|
|
199
|
+
this.client.on('group.changed', (data) => {
|
|
200
|
+
this.countSingleEvent('group.changed');
|
|
201
|
+
void this.storeApplicationEvent('group.changed', data);
|
|
202
|
+
});
|
|
176
203
|
this.client.on('group.invite_received', (data) => this.storeGroupInviteEvent(data));
|
|
177
204
|
for (const event of ['message.read_changed', 'group.read_changed', 'read_state_sync']) {
|
|
178
205
|
this.client.on(event, (payload) => { this.readStateEvents.push({ event, payload: jsonSafe(payload) }); });
|
|
179
206
|
}
|
|
207
|
+
this.client.on('message.peers_snapshot', (payload) => {
|
|
208
|
+
this.peersSnapshots.push({ event: 'message.peers_snapshot', payload: jsonSafe(payload) });
|
|
209
|
+
});
|
|
180
210
|
for (const event of ['ownership.request', 'ownership.pending', 'ownership.progress', 'ownership.completed', 'ownership.rejected', 'ownership.stopped', 'ownership.error', 'ownership.action_required']) {
|
|
181
211
|
this.client.on(event, (payload) => {
|
|
182
212
|
this.ownershipEvents.push({ event, payload: jsonSafe(payload) });
|
|
@@ -189,15 +219,91 @@ export class CrossSdkTsAgent {
|
|
|
189
219
|
this.recordTrace(textOf(payload.org_id), { stage: event, payload });
|
|
190
220
|
});
|
|
191
221
|
}
|
|
222
|
+
this.setBatchEventMode(this.batchEventMode ?? false);
|
|
223
|
+
}
|
|
224
|
+
countSingleEvent(event) {
|
|
225
|
+
this.singleEventCounts ??= { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
|
|
226
|
+
this.singleEventCounts[event] = Number(this.singleEventCounts[event] ?? 0) + 1;
|
|
227
|
+
}
|
|
228
|
+
async storeApplicationEvent(event, value) {
|
|
229
|
+
if (event === 'message.received') {
|
|
230
|
+
this.storeEventLedger(event, value);
|
|
231
|
+
await this.storeInboxItem(this.normalizeMessage(value, true));
|
|
232
|
+
}
|
|
233
|
+
else if (event === 'group.message_created') {
|
|
234
|
+
this.storeEventLedger(event, value);
|
|
235
|
+
await this.storeGroupInboxItem(this.normalizeGroupMessage(value, true));
|
|
236
|
+
}
|
|
237
|
+
else if (event === 'group.changed')
|
|
238
|
+
this.storeGroupChangedPublicEvent(value);
|
|
239
|
+
}
|
|
240
|
+
setBatchEventMode(enabled) {
|
|
241
|
+
this.batchEventMode = enabled;
|
|
242
|
+
if (enabled && this.batchEventClient === this.client && this.batchEventHandlers?.length)
|
|
243
|
+
return;
|
|
244
|
+
for (const { event, handler } of this.batchEventHandlers ?? [])
|
|
245
|
+
this.batchEventClient?.off(event, handler);
|
|
246
|
+
this.batchEventClient = this.client;
|
|
247
|
+
this.batchEventHandlers = [];
|
|
248
|
+
if (!enabled)
|
|
249
|
+
return;
|
|
250
|
+
for (const event of ['message.batch', 'group.message_batch', 'group.event_batch']) {
|
|
251
|
+
const handler = async (value) => {
|
|
252
|
+
const data = jsonObjectOf(value) ?? {};
|
|
253
|
+
const items = JSON.parse(JSON.stringify(jsonSafe(Array.isArray(data.items) ? data.items : [])));
|
|
254
|
+
this.batchEvents ??= [];
|
|
255
|
+
this.batchEvents.push({ observation_id: this.rawPushObservationId, time_ms: Date.now(), event,
|
|
256
|
+
namespace: textOf(data.namespace), source: textOf(data.source), items });
|
|
257
|
+
for (const item of items)
|
|
258
|
+
await this.storeApplicationEvent(textOf(item.event), item.payload);
|
|
259
|
+
};
|
|
260
|
+
this.batchEventHandlers.push({ event, handler });
|
|
261
|
+
this.client.on(event, handler);
|
|
262
|
+
}
|
|
192
263
|
}
|
|
193
264
|
installTransportRpcObserver() {
|
|
265
|
+
const client = this.client;
|
|
266
|
+
const pipeline = client._rpcPipeline;
|
|
267
|
+
this.rpcObservedPipelines ??= new WeakSet();
|
|
268
|
+
this.rpcObservedPulls ??= new WeakMap();
|
|
269
|
+
if (pipeline?.bindActivePullCancellation && !this.rpcObservedPipelines.has(pipeline)) {
|
|
270
|
+
const originalBind = pipeline.bindActivePullCancellation;
|
|
271
|
+
this.rpcObservedPipelines.add(pipeline);
|
|
272
|
+
pipeline.bindActivePullCancellation = (method, params, request, gateKey = '') => {
|
|
273
|
+
const result = originalBind.call(pipeline, method, params, request, gateKey);
|
|
274
|
+
const observed = this.rpcObservedPulls.get(request);
|
|
275
|
+
if (!this.pullBarrier?.preserve || !observed || !['message.pull', 'message.v2.pull'].includes(method)
|
|
276
|
+
|| !textOf(params.sender_aid) || typeof client._delivery?.peerNamespace !== 'function')
|
|
277
|
+
return result;
|
|
278
|
+
const key = gateKey || pipeline.pullGateKeyForCall(method, params);
|
|
279
|
+
const namespace = client._delivery?.peerNamespace(textOf(params.sender_aid));
|
|
280
|
+
const owners = [...pipeline.pullGateStates.values()].flatMap((state) => [...state.activeByNamespace.values()])
|
|
281
|
+
.filter((job) => job.key === key && job.namespace === namespace && job.running && !job.timedOut && !job.invalidated && job.cancel);
|
|
282
|
+
if (owners.length === 1)
|
|
283
|
+
observed.owner = { job: owners[0], cancel: owners[0].cancel, key, namespace: namespace };
|
|
284
|
+
return result;
|
|
285
|
+
};
|
|
286
|
+
}
|
|
194
287
|
const transport = this.client._transport;
|
|
195
288
|
transport?.setTraceMode('diag');
|
|
196
289
|
if (!transport?.call || this.rpcObservedTransports.has(transport))
|
|
197
290
|
return;
|
|
198
291
|
const originalCall = transport.call;
|
|
292
|
+
const originalClose = transport.close;
|
|
199
293
|
this.rpcObservedTransports.add(transport);
|
|
200
|
-
|
|
294
|
+
if (originalClose)
|
|
295
|
+
transport.close = async () => {
|
|
296
|
+
if (!this.pullBarrier?.preserve)
|
|
297
|
+
this.releasePullBarrier();
|
|
298
|
+
try {
|
|
299
|
+
await originalClose.call(transport);
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
if (!this.pullBarrier?.preserve)
|
|
303
|
+
this.releasePullBarrier();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
transport.call = (method, params, timeout, trace, background) => {
|
|
201
307
|
this.storeRawPushRpcMethod({ type: 'rpc', method, request_window: this.rawPushRequestWindow(params) });
|
|
202
308
|
let requestParams = params;
|
|
203
309
|
if (method === 'auth.connect' && !this.inlineRealtimePayloadV1 && jsonObjectOf(params)) {
|
|
@@ -209,8 +315,219 @@ export class CrossSdkTsAgent {
|
|
|
209
315
|
}
|
|
210
316
|
requestParams = request;
|
|
211
317
|
}
|
|
212
|
-
|
|
318
|
+
const generation = this.observationGeneration;
|
|
319
|
+
const span = this.startRpcSpan(method, requestParams);
|
|
320
|
+
const controller = new AbortController();
|
|
321
|
+
let request;
|
|
322
|
+
const observed = {};
|
|
323
|
+
const pending = (async () => {
|
|
324
|
+
try {
|
|
325
|
+
request = originalCall.call(transport, method, requestParams, timeout, trace, background);
|
|
326
|
+
const result = await request;
|
|
327
|
+
if (span) {
|
|
328
|
+
this.finishRpcResponse(span, result);
|
|
329
|
+
if (generation === this.observationGeneration && (this.isObservedPull(method) || this.pullBarrier?.method === method)) {
|
|
330
|
+
await this.waitForPullBarrier(method, controller.signal, timeout ?? transport._timeout ?? 35_000, span, observed.owner);
|
|
331
|
+
}
|
|
332
|
+
if (generation === this.observationGeneration)
|
|
333
|
+
this.injectRPCFailure(method, span);
|
|
334
|
+
span.success = true;
|
|
335
|
+
}
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
if (span) {
|
|
340
|
+
span.success = false;
|
|
341
|
+
span.error = error instanceof Error ? error.name : String(error);
|
|
342
|
+
}
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
if (span)
|
|
347
|
+
span.end_ms = Date.now();
|
|
348
|
+
}
|
|
349
|
+
})();
|
|
350
|
+
pending.cancel = () => { controller.abort(); request?.cancel?.(); };
|
|
351
|
+
this.rpcObservedPulls.set(pending, observed);
|
|
352
|
+
return pending;
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
storeEventLedger(event, value) {
|
|
356
|
+
const data = jsonObjectOf(value) ?? {};
|
|
357
|
+
const internal = this.client;
|
|
358
|
+
const payload = JSON.parse(stableStringify(jsonSafe(data.payload ?? null)));
|
|
359
|
+
this.eventLedger.push({
|
|
360
|
+
observation_id: this.rawPushObservationId, event, event_index: this.eventLedger.length + 1, time_ms: Date.now(),
|
|
361
|
+
scope: event === 'message.received' ? 'p2p' : 'group', receiver_aid: this.aid,
|
|
362
|
+
device_id: textOf(internal._deviceId), slot_id: textOf(internal._slotId ?? this.slotId),
|
|
363
|
+
sender_aid: textOf(data.sender_aid ?? data.from_aid ?? data.from), group_id: textOf(data.group_id),
|
|
364
|
+
message_id: textOf(data.message_id ?? data.id), seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
|
|
365
|
+
storage: textOf(data.storage), volatile_cursor: textOf(data.volatile_cursor),
|
|
366
|
+
payload, payload_sha256: sha256Json(payload), need_render: data.need_render !== false,
|
|
367
|
+
...(Object.prototype.hasOwnProperty.call(data, 'mention_mode') ? { mention_mode: data.mention_mode } : {}),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
isObservedPull(method) {
|
|
371
|
+
return ['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.pull_messages', 'group.pull_events', 'group.events'].includes(method);
|
|
372
|
+
}
|
|
373
|
+
startRpcSpan(method, value) {
|
|
374
|
+
if (!this.isObservedPull(method) && !['message.peers', 'message.ack', 'message.v2.ack', 'group.ack', 'group.v2.ack', 'group.ack_messages', 'group.ack_events'].includes(method))
|
|
375
|
+
return null;
|
|
376
|
+
const params = jsonObjectOf(value) ?? {};
|
|
377
|
+
const senderAid = textOf(params.sender_aid), groupId = textOf(params.group_id ?? params.group_aid);
|
|
378
|
+
const namespace = method === 'message.peers' ? '' : method.startsWith('group.')
|
|
379
|
+
? groupId ? `${method === 'group.pull_events' || method === 'group.events' || method === 'group.ack_events' ? 'group_event' : 'group'}:${groupId}` : ''
|
|
380
|
+
: senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
|
|
381
|
+
const span = {
|
|
382
|
+
observation_id: this.rawPushObservationId, span_id: crypto.randomUUID(), boundary: 'transport_call', method,
|
|
383
|
+
start_ms: Date.now(), response_ms: null, end_ms: null, success: null,
|
|
384
|
+
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,
|
|
385
|
+
group_id: groupId, namespace, request_window: this.observedRequestWindow(params),
|
|
386
|
+
response_message_count: 0, response_volatile_count: 0, response_event_count: 0,
|
|
387
|
+
response_item_keys: [], response_event_keys: [],
|
|
388
|
+
has_more: null, persistent_has_more: null, volatile_has_more: null, has_more_persistent: null, has_more_volatile: null, effective_ack_seq: null,
|
|
213
389
|
};
|
|
390
|
+
this.rpcSpans.push(span);
|
|
391
|
+
return span;
|
|
392
|
+
}
|
|
393
|
+
observedRequestWindow(params) {
|
|
394
|
+
const window = {};
|
|
395
|
+
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']) {
|
|
396
|
+
if (typeof params[key] === 'number' && Number.isSafeInteger(params[key]))
|
|
397
|
+
window[key] = params[key];
|
|
398
|
+
}
|
|
399
|
+
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']) {
|
|
400
|
+
if (typeof params[key] === 'string')
|
|
401
|
+
window[key] = params[key];
|
|
402
|
+
}
|
|
403
|
+
for (const key of ['force', 'pending_only'])
|
|
404
|
+
if (typeof params[key] === 'boolean')
|
|
405
|
+
window[key] = params[key];
|
|
406
|
+
for (const key of ['volatile_cursors', 'volatile_ids']) {
|
|
407
|
+
if (Array.isArray(params[key]))
|
|
408
|
+
window[key] = params[key].filter((item) => typeof item === 'string');
|
|
409
|
+
}
|
|
410
|
+
return window;
|
|
411
|
+
}
|
|
412
|
+
finishRpcResponse(span, value) {
|
|
413
|
+
const response = jsonObjectOf(value) ?? {};
|
|
414
|
+
const messages = Array.isArray(response.messages) ? response.messages : [];
|
|
415
|
+
span.response_ms = Date.now();
|
|
416
|
+
span.response_message_count = messages.length;
|
|
417
|
+
span.response_volatile_count = messages.filter((item) => jsonObjectOf(item)?.storage === 'volatile').length;
|
|
418
|
+
span.response_event_count = Array.isArray(response.events) ? response.events.length : 0;
|
|
419
|
+
const sequenceOf = (raw, ...keys) => {
|
|
420
|
+
for (const key of keys) {
|
|
421
|
+
const value = raw[key];
|
|
422
|
+
if (typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value))
|
|
423
|
+
return value;
|
|
424
|
+
}
|
|
425
|
+
return 0;
|
|
426
|
+
};
|
|
427
|
+
const itemKeys = new Map();
|
|
428
|
+
for (const key of ['messages', 'volatile_messages', 'volatile']) {
|
|
429
|
+
for (const item of Array.isArray(response[key]) ? response[key] : []) {
|
|
430
|
+
const raw = jsonObjectOf(item) ?? {}, cursor = textOf(raw.volatile_cursor);
|
|
431
|
+
const identity = { message_id: textOf(raw.message_id ?? raw.id),
|
|
432
|
+
seq: sequenceOf(raw, 'seq', 'message_seq', 'msg_seq'),
|
|
433
|
+
storage: typeof raw.storage === 'string' ? raw.storage : cursor ? 'volatile' : 'persistent', volatile_cursor: cursor };
|
|
434
|
+
itemKeys.set(JSON.stringify(identity), identity);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
span.response_item_keys = [...itemKeys.values()];
|
|
438
|
+
span.response_event_keys = (Array.isArray(response.events) ? response.events : []).map((item) => {
|
|
439
|
+
const raw = jsonObjectOf(item) ?? {};
|
|
440
|
+
return { event_id: textOf(raw.event_id), event_seq: sequenceOf(raw, 'event_seq', 'seq') };
|
|
441
|
+
});
|
|
442
|
+
span.has_more = typeof response.has_more === 'boolean' ? response.has_more : null;
|
|
443
|
+
span.has_more_persistent = typeof response.persistent_has_more === 'boolean' ? response.persistent_has_more : null;
|
|
444
|
+
span.has_more_volatile = typeof response.volatile_has_more === 'boolean' ? response.volatile_has_more : null;
|
|
445
|
+
span.persistent_has_more = span.has_more_persistent;
|
|
446
|
+
span.volatile_has_more = span.has_more_volatile;
|
|
447
|
+
for (const key of ['peer_ack_seq', 'effective_ack_seq', 'server_ack_seq', 'ack_seq', 'up_to_seq', 'acked_seq']) {
|
|
448
|
+
if (typeof response[key] === 'number' && Number.isSafeInteger(response[key]))
|
|
449
|
+
span[key] = response[key];
|
|
450
|
+
}
|
|
451
|
+
const effective = response.peer_ack_seq ?? response.effective_ack_seq ?? response.ack_seq ?? response.up_to_seq ?? response.acked_seq;
|
|
452
|
+
if (typeof effective === 'number' && Number.isSafeInteger(effective))
|
|
453
|
+
span.effective_ack_seq = effective;
|
|
454
|
+
const volatileAck = jsonObjectOf(response.volatile_ack);
|
|
455
|
+
if (volatileAck) {
|
|
456
|
+
const ack = {};
|
|
457
|
+
for (const key of ['accepted', 'expired'])
|
|
458
|
+
if (typeof volatileAck[key] === 'number' && Number.isSafeInteger(volatileAck[key]))
|
|
459
|
+
ack[key] = volatileAck[key];
|
|
460
|
+
if (typeof volatileAck.storage === 'string')
|
|
461
|
+
ack.storage = volatileAck.storage;
|
|
462
|
+
span.volatile_ack = ack;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
releasePullBarrier() {
|
|
466
|
+
const barrier = this.pullBarrier;
|
|
467
|
+
if (!barrier || barrier.released)
|
|
468
|
+
return;
|
|
469
|
+
barrier.released = true;
|
|
470
|
+
barrier.release();
|
|
471
|
+
}
|
|
472
|
+
injectRPCFailure(method, span) {
|
|
473
|
+
const failure = this.rpcFailures?.find((item) => !item.hit && item.method === method && item.sender_aid === span.sender_aid);
|
|
474
|
+
if (!failure)
|
|
475
|
+
return;
|
|
476
|
+
failure.hit = true;
|
|
477
|
+
span.injected_failure = failure.kind ?? 'error';
|
|
478
|
+
if (failure.kind === 'timeout')
|
|
479
|
+
throw new TimeoutError('cross-sdk 响应后一次超时');
|
|
480
|
+
throw new Error('cross-sdk 响应后一次失败');
|
|
481
|
+
}
|
|
482
|
+
resetObservationLedgers(pullBarrierCount = 0, scope = 'all', controls = {}) {
|
|
483
|
+
this.releasePullBarrier();
|
|
484
|
+
this.observationGeneration = (this.observationGeneration ?? 0) + 1;
|
|
485
|
+
this.eventLedger = [];
|
|
486
|
+
this.batchEvents = [];
|
|
487
|
+
this.singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
|
|
488
|
+
this.rpcSpans = [];
|
|
489
|
+
this.rpcFailures = Array.isArray(controls.rpc_failures) ? controls.rpc_failures.map((item) => ({ ...jsonObjectOf(item) })) : [];
|
|
490
|
+
this.pullBarrier = null;
|
|
491
|
+
if (Number.isSafeInteger(pullBarrierCount) && pullBarrierCount > 0) {
|
|
492
|
+
let release;
|
|
493
|
+
const promise = new Promise((resolve) => { release = resolve; });
|
|
494
|
+
this.pullBarrier = { target: pullBarrierCount, scope: scope === 'p2p' ? 'p2p' : 'all', admitted: 0, waiting: 0, released: false, promise, release,
|
|
495
|
+
sender: textOf(controls.pull_barrier_sender_aid), method: textOf(controls.pull_barrier_method), preserve: controls.preserve_pull_barrier === true };
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
async waitForPullBarrier(method, signal, timeout, span, owner) {
|
|
499
|
+
const barrier = this.pullBarrier;
|
|
500
|
+
if (!barrier || barrier.released || barrier.admitted >= barrier.target)
|
|
501
|
+
return;
|
|
502
|
+
if (barrier.scope === 'p2p' && method !== 'message.pull' && method !== 'message.v2.pull' && barrier.method !== method)
|
|
503
|
+
return;
|
|
504
|
+
if ((barrier.sender && barrier.sender !== span.sender_aid) || (barrier.method && barrier.method !== method))
|
|
505
|
+
return;
|
|
506
|
+
barrier.admitted += 1;
|
|
507
|
+
barrier.waiting += 1;
|
|
508
|
+
if (barrier.preserve) {
|
|
509
|
+
barrier.heldMethod = method;
|
|
510
|
+
barrier.owner = owner;
|
|
511
|
+
barrier.span = span;
|
|
512
|
+
}
|
|
513
|
+
let timer;
|
|
514
|
+
let abort;
|
|
515
|
+
try {
|
|
516
|
+
await Promise.race([barrier.promise, new Promise((_resolve, reject) => {
|
|
517
|
+
abort = () => reject(new Error('pull observation barrier cancelled'));
|
|
518
|
+
if (!barrier.preserve)
|
|
519
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
520
|
+
if (signal.aborted && !barrier.preserve)
|
|
521
|
+
abort();
|
|
522
|
+
timer = setTimeout(() => reject(new Error('pull observation barrier timeout')), Math.max(1, timeout));
|
|
523
|
+
})]);
|
|
524
|
+
}
|
|
525
|
+
finally {
|
|
526
|
+
if (timer)
|
|
527
|
+
clearTimeout(timer);
|
|
528
|
+
signal.removeEventListener('abort', abort);
|
|
529
|
+
barrier.waiting -= 1;
|
|
530
|
+
}
|
|
214
531
|
}
|
|
215
532
|
storeRawPushObservation(rawEvent, scope, defaultKind, value) {
|
|
216
533
|
const data = jsonObjectOf(value) ?? {};
|
|
@@ -299,6 +616,7 @@ export class CrossSdkTsAgent {
|
|
|
299
616
|
};
|
|
300
617
|
}
|
|
301
618
|
async close() {
|
|
619
|
+
this.resetObservationLedgers();
|
|
302
620
|
try {
|
|
303
621
|
await this.client.close();
|
|
304
622
|
}
|
|
@@ -489,6 +807,7 @@ export class CrossSdkTsAgent {
|
|
|
489
807
|
decrypted,
|
|
490
808
|
encrypted: Boolean(data.e2ee ?? data.encrypted),
|
|
491
809
|
seq: Number(data.seq ?? data.message_seq ?? 0) || 0,
|
|
810
|
+
need_render: data.need_render !== false,
|
|
492
811
|
ack_seq: Number(data.ack_seq ?? 0) || 0,
|
|
493
812
|
storage: textOf(data.storage),
|
|
494
813
|
volatile_cursor: textOf(data.volatile_cursor),
|
|
@@ -514,6 +833,7 @@ export class CrossSdkTsAgent {
|
|
|
514
833
|
decrypted,
|
|
515
834
|
encrypted: Boolean(data.e2ee ?? data.encrypted),
|
|
516
835
|
seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
|
|
836
|
+
need_render: data.need_render !== false,
|
|
517
837
|
error_code: errorCode,
|
|
518
838
|
raw_sha256: sha256Json(data),
|
|
519
839
|
...metadata,
|
|
@@ -543,6 +863,7 @@ export class CrossSdkTsAgent {
|
|
|
543
863
|
}
|
|
544
864
|
if (req.method === 'POST' && url.pathname === '/reset') {
|
|
545
865
|
const body = await readJson(req);
|
|
866
|
+
this.resetObservationLedgers();
|
|
546
867
|
const traceId = textOf(body.trace_id ?? '');
|
|
547
868
|
if (traceId) {
|
|
548
869
|
this.inbox = this.inbox.filter((item) => item.trace_id !== traceId);
|
|
@@ -553,6 +874,7 @@ export class CrossSdkTsAgent {
|
|
|
553
874
|
else {
|
|
554
875
|
this.inbox = [];
|
|
555
876
|
this.groupInbox = [];
|
|
877
|
+
this.peersSnapshots = [];
|
|
556
878
|
this.traces.clear();
|
|
557
879
|
this.sendResults.clear();
|
|
558
880
|
}
|
|
@@ -561,11 +883,25 @@ export class CrossSdkTsAgent {
|
|
|
561
883
|
}
|
|
562
884
|
if (req.method === 'POST' && url.pathname === '/test/raw-push/reset') {
|
|
563
885
|
const body = await readJson(req);
|
|
886
|
+
if ((body.pull_barrier_count !== undefined && (typeof body.pull_barrier_count !== 'number' || !Number.isSafeInteger(body.pull_barrier_count) || body.pull_barrier_count < 0)) ||
|
|
887
|
+
(body.pull_barrier_scope !== undefined && !['all', 'p2p'].includes(body.pull_barrier_scope)) ||
|
|
888
|
+
(body.observation_id !== undefined && typeof body.observation_id !== 'string') ||
|
|
889
|
+
(body.release_pull_barrier !== undefined && typeof body.release_pull_barrier !== 'boolean')) {
|
|
890
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid observation parameters' });
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
if (body.release_pull_barrier === true) {
|
|
894
|
+
this.releasePullBarrier();
|
|
895
|
+
sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
this.resetObservationLedgers(body.pull_barrier_count, textOf(body.pull_barrier_scope), body);
|
|
564
899
|
this.rawPushObservations = [];
|
|
565
900
|
this.rawPushRpcMethods = [];
|
|
566
901
|
this.groupChangedEvents = [];
|
|
567
902
|
this.groupInviteEvents = [];
|
|
568
903
|
this.readStateEvents = [];
|
|
904
|
+
this.peersSnapshots = [];
|
|
569
905
|
this.rawPushObservationId = textOf(body.observation_id ?? '');
|
|
570
906
|
sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
|
|
571
907
|
return;
|
|
@@ -584,6 +920,23 @@ export class CrossSdkTsAgent {
|
|
|
584
920
|
let rpcEntries = [...this.rawPushRpcMethods];
|
|
585
921
|
let groupChangedEvents = [...this.groupChangedEvents];
|
|
586
922
|
let groupInviteEvents = [...this.groupInviteEvents];
|
|
923
|
+
let eventLedger = [...this.eventLedger ?? []];
|
|
924
|
+
let rpcSpans = [...this.rpcSpans ?? []];
|
|
925
|
+
let batchEvents = [...this.batchEvents ?? []];
|
|
926
|
+
if (observationId)
|
|
927
|
+
batchEvents = batchEvents.filter((item) => textOf(item.observation_id) === observationId);
|
|
928
|
+
if (observationId)
|
|
929
|
+
eventLedger = eventLedger.filter((item) => textOf(item.observation_id) === observationId);
|
|
930
|
+
if (observationId)
|
|
931
|
+
rpcSpans = rpcSpans.filter((item) => textOf(item.observation_id) === observationId);
|
|
932
|
+
if (filters.scope)
|
|
933
|
+
eventLedger = eventLedger.filter((item) => textOf(item.scope) === filters.scope);
|
|
934
|
+
if (filters.scope)
|
|
935
|
+
rpcSpans = rpcSpans.filter((item) => textOf(item.method).startsWith(filters.scope === 'p2p' ? 'message.' : 'group.'));
|
|
936
|
+
if (filters.group_id)
|
|
937
|
+
eventLedger = eventLedger.filter((item) => textOf(item.group_id) === filters.group_id);
|
|
938
|
+
if (filters.group_id)
|
|
939
|
+
rpcSpans = rpcSpans.filter((item) => textOf(item.group_id) === filters.group_id);
|
|
587
940
|
if (observationId)
|
|
588
941
|
rpcEntries = rpcEntries.filter((item) => textOf(item.observation_id) === observationId);
|
|
589
942
|
if (observationId)
|
|
@@ -600,11 +953,18 @@ export class CrossSdkTsAgent {
|
|
|
600
953
|
for (const method of rpcMethods)
|
|
601
954
|
rpcMethodCounts[method] = Number(rpcMethodCounts[method] ?? 0) + 1;
|
|
602
955
|
sendJson(res, 200, {
|
|
603
|
-
ok: true, items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
|
|
956
|
+
ok: true, time_ms: Date.now(), items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
|
|
604
957
|
rpc_calls: rpcEntries,
|
|
605
958
|
group_changed_events: groupChangedEvents.slice(-limit),
|
|
606
959
|
group_invite_events: groupInviteEvents.slice(-limit),
|
|
607
960
|
read_state_events: this.readStateEvents.slice(-limit),
|
|
961
|
+
peers_snapshots: this.peersSnapshots.slice(-limit),
|
|
962
|
+
event_ledger: eventLedger,
|
|
963
|
+
rpc_spans: rpcSpans,
|
|
964
|
+
batch_event_mode: this.batchEventMode ?? false,
|
|
965
|
+
batch_events: batchEvents,
|
|
966
|
+
single_event_counts: this.singleEventCounts ?? { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 },
|
|
967
|
+
pull_barrier: { target: this.pullBarrier?.target ?? 0, waiting: this.pullBarrier?.waiting ?? 0, released: this.pullBarrier?.released ?? true, scope: this.pullBarrier?.scope ?? 'all' },
|
|
608
968
|
});
|
|
609
969
|
return;
|
|
610
970
|
}
|
|
@@ -896,6 +1256,8 @@ export class CrossSdkTsAgent {
|
|
|
896
1256
|
};
|
|
897
1257
|
if (body.ttl !== undefined)
|
|
898
1258
|
params.ttl = Number(body.ttl);
|
|
1259
|
+
if (body.need_render !== undefined)
|
|
1260
|
+
params.need_render = body.need_render;
|
|
899
1261
|
try {
|
|
900
1262
|
const result = await this.client.call('message.send', params);
|
|
901
1263
|
const resultObj = result && typeof result === 'object' ? result : {};
|
|
@@ -931,6 +1293,10 @@ export class CrossSdkTsAgent {
|
|
|
931
1293
|
const params = {};
|
|
932
1294
|
if (seq > 0)
|
|
933
1295
|
params.seq = seq;
|
|
1296
|
+
if (textOf(body.sender_aid).trim())
|
|
1297
|
+
params.sender_aid = textOf(body.sender_aid).trim();
|
|
1298
|
+
if (body.cursor_scope !== undefined)
|
|
1299
|
+
params.cursor_scope = body.cursor_scope;
|
|
934
1300
|
if (Array.isArray(body.volatile_cursors))
|
|
935
1301
|
params.volatile_cursors = body.volatile_cursors;
|
|
936
1302
|
else if (textOf(body.volatile_cursor))
|
|
@@ -952,9 +1318,26 @@ export class CrossSdkTsAgent {
|
|
|
952
1318
|
}
|
|
953
1319
|
async pull(req, res) {
|
|
954
1320
|
const body = await readJson(req);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1321
|
+
if ((body.limit !== undefined && (typeof body.limit !== 'number' || !Number.isSafeInteger(body.limit) || body.limit < 1 || body.limit > 200)) ||
|
|
1322
|
+
(body.max_pages !== undefined && (typeof body.max_pages !== 'number' || !Number.isSafeInteger(body.max_pages) || body.max_pages < 1)) ||
|
|
1323
|
+
(body.after_seq !== undefined && (typeof body.after_seq !== 'number' || !Number.isSafeInteger(body.after_seq) || body.after_seq < 0)) ||
|
|
1324
|
+
['sender_aid', 'cursor_scope', 'after_volatile_cursor'].some((key) => body[key] !== undefined && typeof body[key] !== 'string') ||
|
|
1325
|
+
(body.force !== undefined && typeof body.force !== 'boolean') ||
|
|
1326
|
+
(body.window_mode !== undefined && !['forward', 'tail'].includes(body.window_mode))) {
|
|
1327
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid pull parameters' });
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
const senderAid = textOf(body.sender_aid).trim();
|
|
1331
|
+
const limit = body.limit ?? 50;
|
|
1332
|
+
const params = { limit };
|
|
1333
|
+
if (senderAid)
|
|
1334
|
+
params.sender_aid = senderAid;
|
|
1335
|
+
if (body.after_seq !== undefined)
|
|
1336
|
+
params.after_seq = body.after_seq;
|
|
1337
|
+
for (const key of ['cursor_scope', 'force', 'window_mode', 'max_pages']) {
|
|
1338
|
+
if (body[key] !== undefined)
|
|
1339
|
+
params[key] = body[key];
|
|
1340
|
+
}
|
|
958
1341
|
if (textOf(body.after_volatile_cursor))
|
|
959
1342
|
params.after_volatile_cursor = textOf(body.after_volatile_cursor);
|
|
960
1343
|
try {
|
|
@@ -976,6 +1359,20 @@ export class CrossSdkTsAgent {
|
|
|
976
1359
|
}
|
|
977
1360
|
async messageCall(req, res) {
|
|
978
1361
|
const body = await readJson(req);
|
|
1362
|
+
if (['start', 'status', 'detach'].includes(textOf(body.action))) {
|
|
1363
|
+
this.messageOperations ??= new Map();
|
|
1364
|
+
const id = textOf(body.operation_id);
|
|
1365
|
+
if (body.action === 'start') {
|
|
1366
|
+
const operation = { ok: true, operation_id: id, status: 'running', started_ms: Date.now() };
|
|
1367
|
+
this.messageOperations.set(id, operation);
|
|
1368
|
+
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) }));
|
|
1369
|
+
}
|
|
1370
|
+
const operation = this.messageOperations.get(id);
|
|
1371
|
+
if (body.action === 'detach')
|
|
1372
|
+
Object.assign(operation, { detached: true, cancellation: 'application_wait_only', detached_ms: Date.now() });
|
|
1373
|
+
sendJson(res, 200, operation);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
979
1376
|
const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
|
|
980
1377
|
const method = textOf(body.method).trim();
|
|
981
1378
|
const params = jsonObjectOf(body.params) ?? {};
|
|
@@ -1007,15 +1404,15 @@ export class CrossSdkTsAgent {
|
|
|
1007
1404
|
sendJson(res, 500, error);
|
|
1008
1405
|
}
|
|
1009
1406
|
}
|
|
1010
|
-
syncNamespace(scope, groupId = '') {
|
|
1407
|
+
syncNamespace(scope, groupId = '', senderAid = '') {
|
|
1011
1408
|
if (scope === 'p2p')
|
|
1012
|
-
return `p2p:${this.aid}`;
|
|
1409
|
+
return senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
|
|
1013
1410
|
if (scope === 'group' && groupId)
|
|
1014
1411
|
return `group:${groupId}`;
|
|
1015
1412
|
throw new Error('scope must be p2p, or group with group_id');
|
|
1016
1413
|
}
|
|
1017
|
-
syncStateValue(scope, groupId = '') {
|
|
1018
|
-
const namespace = this.syncNamespace(scope, groupId);
|
|
1414
|
+
syncStateValue(scope, groupId = '', senderAid = '') {
|
|
1415
|
+
const namespace = this.syncNamespace(scope, groupId, senderAid);
|
|
1019
1416
|
const tracker = this.client._seqTracker;
|
|
1020
1417
|
if (!tracker)
|
|
1021
1418
|
throw new Error('SeqTracker is unavailable');
|
|
@@ -1024,9 +1421,10 @@ export class CrossSdkTsAgent {
|
|
|
1024
1421
|
syncState(url, res) {
|
|
1025
1422
|
const scope = textOf(url.searchParams.get('scope') || 'p2p').trim().toLowerCase();
|
|
1026
1423
|
const groupId = textOf(url.searchParams.get('group_id') || '').trim();
|
|
1424
|
+
const senderAid = textOf(url.searchParams.get('sender_aid') || '').trim();
|
|
1027
1425
|
try {
|
|
1028
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
1029
|
-
sendJson(res, 200, { ok: true, scope, ...snapshot });
|
|
1426
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
1427
|
+
sendJson(res, 200, { ok: true, scope, ...snapshot, peers_snapshots: this.peersSnapshots });
|
|
1030
1428
|
}
|
|
1031
1429
|
catch (err) {
|
|
1032
1430
|
sendJson(res, 400, {
|
|
@@ -1046,27 +1444,24 @@ export class CrossSdkTsAgent {
|
|
|
1046
1444
|
const body = await readJson(req);
|
|
1047
1445
|
const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
|
|
1048
1446
|
const groupId = textOf(body.group_id || '').trim();
|
|
1447
|
+
const senderAid = textOf(body.sender_aid || '').trim();
|
|
1049
1448
|
const afterSeq = Number(body.after_seq ?? 0) || 0;
|
|
1050
1449
|
const limit = Number(body.limit ?? 50) || 50;
|
|
1051
1450
|
try {
|
|
1052
|
-
const params = {
|
|
1053
|
-
if (
|
|
1054
|
-
params.
|
|
1451
|
+
const params = { limit, window_mode: tail ? 'tail' : 'forward' };
|
|
1452
|
+
if (scope === 'group' || body.after_seq !== undefined)
|
|
1453
|
+
params.after_seq = afterSeq;
|
|
1454
|
+
if (scope === 'p2p' && senderAid)
|
|
1455
|
+
params.sender_aid = senderAid;
|
|
1456
|
+
for (const key of ['cursor_scope', 'force', 'max_pages', 'after_volatile_cursor']) {
|
|
1457
|
+
if (body[key] !== undefined)
|
|
1458
|
+
params[key] = body[key];
|
|
1459
|
+
}
|
|
1055
1460
|
const method = scope === 'group' ? 'group.pull' : 'message.pull';
|
|
1056
1461
|
if (scope === 'group')
|
|
1057
1462
|
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
|
-
}
|
|
1463
|
+
this.syncNamespace(scope, groupId, senderAid);
|
|
1464
|
+
const result = await this.client.call(method, params);
|
|
1070
1465
|
const resultObj = jsonObjectOf(result) ?? {};
|
|
1071
1466
|
const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
|
|
1072
1467
|
if (tail) {
|
|
@@ -1077,7 +1472,7 @@ export class CrossSdkTsAgent {
|
|
|
1077
1472
|
await this.storeInboxItem(this.normalizeMessage(msg, true));
|
|
1078
1473
|
}
|
|
1079
1474
|
}
|
|
1080
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
1475
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
1081
1476
|
sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
|
|
1082
1477
|
}
|
|
1083
1478
|
catch (err) {
|
|
@@ -1093,6 +1488,7 @@ export class CrossSdkTsAgent {
|
|
|
1093
1488
|
const body = await readJson(req);
|
|
1094
1489
|
const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
|
|
1095
1490
|
const groupId = textOf(body.group_id || '').trim();
|
|
1491
|
+
const senderAid = textOf(body.sender_aid || '').trim();
|
|
1096
1492
|
const beforeSeq = Number(body.before_seq ?? 0) || 0;
|
|
1097
1493
|
const limit = Number(body.limit ?? 50) || 50;
|
|
1098
1494
|
try {
|
|
@@ -1100,9 +1496,13 @@ export class CrossSdkTsAgent {
|
|
|
1100
1496
|
const method = scope === 'group' ? 'group.history' : 'message.history';
|
|
1101
1497
|
if (scope === 'group')
|
|
1102
1498
|
params.group_id = groupId;
|
|
1103
|
-
|
|
1499
|
+
if (scope === 'p2p' && senderAid)
|
|
1500
|
+
params.sender_aid = senderAid;
|
|
1501
|
+
if (body.cursor_scope !== undefined)
|
|
1502
|
+
params.cursor_scope = body.cursor_scope;
|
|
1503
|
+
this.syncNamespace(scope, groupId, senderAid);
|
|
1104
1504
|
const result = await this.client.call(method, params);
|
|
1105
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
1505
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
1106
1506
|
sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
|
|
1107
1507
|
}
|
|
1108
1508
|
catch (err) {
|
|
@@ -1117,7 +1517,25 @@ export class CrossSdkTsAgent {
|
|
|
1117
1517
|
async sessionDisconnect(res) {
|
|
1118
1518
|
try {
|
|
1119
1519
|
this.ready = false;
|
|
1120
|
-
|
|
1520
|
+
const barrier = this.pullBarrier;
|
|
1521
|
+
let restore;
|
|
1522
|
+
if (barrier?.preserve && barrier.waiting === 1 && !barrier.released && ['message.pull', 'message.v2.pull'].includes(barrier.heldMethod ?? '')) {
|
|
1523
|
+
const binding = barrier.owner, pipeline = this.client._rpcPipeline;
|
|
1524
|
+
const owners = [...(pipeline?.pullGateStates.values() ?? [])].flatMap((state) => [...state.activeByNamespace.values()])
|
|
1525
|
+
.filter((job) => job === binding?.job && job.key === binding.key && job.namespace === binding.namespace && job.running && !job.timedOut && !job.invalidated && job.cancel === binding.cancel);
|
|
1526
|
+
if (owners.length !== 1)
|
|
1527
|
+
throw new Error('保留响应无法定位唯一受跟踪的 pull owner');
|
|
1528
|
+
const owner = owners[0], settled = owner.settled;
|
|
1529
|
+
owner.settled = Promise.resolve();
|
|
1530
|
+
restore = () => { owner.settled = settled; };
|
|
1531
|
+
barrier.span.barrier_owner_isolated = true;
|
|
1532
|
+
}
|
|
1533
|
+
try {
|
|
1534
|
+
await this.client.disconnect();
|
|
1535
|
+
}
|
|
1536
|
+
finally {
|
|
1537
|
+
restore?.();
|
|
1538
|
+
}
|
|
1121
1539
|
sendJson(res, 200, { ok: true, state: textOf(this.client.state) });
|
|
1122
1540
|
}
|
|
1123
1541
|
catch (err) {
|
|
@@ -1130,14 +1548,19 @@ export class CrossSdkTsAgent {
|
|
|
1130
1548
|
}
|
|
1131
1549
|
async sessionConnect(req, res) {
|
|
1132
1550
|
const body = await readJson(req);
|
|
1551
|
+
if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
|
|
1552
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1133
1555
|
const backgroundSync = body.background_sync === true;
|
|
1134
1556
|
if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
|
|
1135
1557
|
this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
|
|
1136
1558
|
}
|
|
1137
1559
|
try {
|
|
1560
|
+
this.setBatchEventMode(typeof body.batch_events === 'boolean' ? body.batch_events : this.batchEventMode ?? false);
|
|
1138
1561
|
await this.client.connect({ auto_reconnect: false, background_sync: backgroundSync });
|
|
1139
1562
|
this.ready = true;
|
|
1140
|
-
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
|
|
1563
|
+
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
|
|
1141
1564
|
}
|
|
1142
1565
|
catch (err) {
|
|
1143
1566
|
sendJson(res, 500, {
|
|
@@ -1149,19 +1572,25 @@ export class CrossSdkTsAgent {
|
|
|
1149
1572
|
}
|
|
1150
1573
|
async sessionRestart(req, res) {
|
|
1151
1574
|
const body = await readJson(req);
|
|
1575
|
+
if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
|
|
1576
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1152
1579
|
const backgroundSync = body.background_sync === true;
|
|
1153
1580
|
if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
|
|
1154
1581
|
this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
|
|
1155
1582
|
}
|
|
1156
1583
|
this.ready = false;
|
|
1157
1584
|
try {
|
|
1585
|
+
if (typeof body.batch_events === 'boolean')
|
|
1586
|
+
this.batchEventMode = body.batch_events;
|
|
1158
1587
|
await this.client.close();
|
|
1159
1588
|
this.client = this.newClient();
|
|
1160
1589
|
this.bindClientEvents();
|
|
1161
1590
|
await this.ensureConnected(backgroundSync);
|
|
1162
1591
|
this.startupError = '';
|
|
1163
1592
|
this.ready = true;
|
|
1164
|
-
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
|
|
1593
|
+
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
|
|
1165
1594
|
}
|
|
1166
1595
|
catch (err) {
|
|
1167
1596
|
this.startupError = `${err instanceof Error ? err.name : 'Error'}: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -2053,6 +2482,7 @@ export class CrossSdkTsAgent {
|
|
|
2053
2482
|
payload,
|
|
2054
2483
|
encrypt: e2ee,
|
|
2055
2484
|
message_id: messageId,
|
|
2485
|
+
...(body.need_render !== undefined ? { need_render: body.need_render } : {}),
|
|
2056
2486
|
});
|
|
2057
2487
|
const resultObj = result && typeof result === 'object' && !Array.isArray(result) ? result : {};
|
|
2058
2488
|
const response = {
|