@agentunion/fastaun-browser 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.d.ts +1 -0
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +5 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +25 -1
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +148 -71
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +55 -8
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +2 -1
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +4981 -1219
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +21 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +101 -16
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +1172 -160
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +122 -48
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +22 -3
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +1258 -649
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +23 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +391 -87
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +19 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +146 -23
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +42 -1
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -1
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +30 -13
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +19 -2
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +193 -6
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +15 -2
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +308 -27
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.d.ts.map +1 -1
- package/dist/org-protocol.js +757 -11
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +21 -4
- package/dist/ownership-v21.d.ts.map +1 -1
- package/dist/ownership-v21.js +49 -16
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +30 -0
- package/dist/ownership-workflow.d.ts.map +1 -1
- package/dist/ownership-workflow.js +412 -54
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/seq-tracker.d.ts +2 -0
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +3 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +60 -3
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +433 -37
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +62 -30
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import * as path from 'node:path';
|
|
|
8
8
|
import { URL, fileURLToPath } from 'node:url';
|
|
9
9
|
import { AID, AIDStore, AUNClient, __version__ } from '../index.js';
|
|
10
10
|
import { publicKeyFingerprint } from '../cert-utils.js';
|
|
11
|
-
import { StateError } from '../errors.js';
|
|
11
|
+
import { StateError, TimeoutError } from '../errors.js';
|
|
12
12
|
import { OwnershipStorageAdapter } from '../ownership-v21.js';
|
|
13
13
|
import { ownershipErrorCode, ownershipTaskResult } from '../ownership-workflow.js';
|
|
14
14
|
import { canonicalJson, digest, buildRequestBinding, verifyMembershipBinding, ProtocolError } from '../org-protocol.js';
|
|
@@ -141,14 +141,28 @@ export class CrossSdkJsAgent {
|
|
|
141
141
|
rawPushObservations = [];
|
|
142
142
|
rawPushObservationId = '';
|
|
143
143
|
rawPushRpcMethods = [];
|
|
144
|
+
eventLedger = [];
|
|
145
|
+
rpcSpans = [];
|
|
146
|
+
observationGeneration = 0;
|
|
147
|
+
pullBarrier = null;
|
|
148
|
+
rpcFailures = [];
|
|
149
|
+
messageOperations = new Map();
|
|
144
150
|
rpcObservedTransports = new WeakSet();
|
|
151
|
+
rpcObservedPipelines = new WeakSet();
|
|
152
|
+
rpcObservedPulls = new WeakMap();
|
|
145
153
|
groupChangedEvents = [];
|
|
146
154
|
groupInviteEvents = [];
|
|
147
155
|
readStateEvents = [];
|
|
156
|
+
peersSnapshots = [];
|
|
148
157
|
ownershipEvents = [];
|
|
149
158
|
traces = new Map();
|
|
150
159
|
sendResults = new Map();
|
|
151
160
|
inlineRealtimePayloadV1 = true;
|
|
161
|
+
batchEventMode = false;
|
|
162
|
+
batchEvents = [];
|
|
163
|
+
singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
|
|
164
|
+
clientEventsBound = new WeakSet();
|
|
165
|
+
batchEventHandlers = new WeakMap();
|
|
152
166
|
constructor() {
|
|
153
167
|
this.client = this.newClient();
|
|
154
168
|
}
|
|
@@ -166,28 +180,41 @@ export class CrossSdkJsAgent {
|
|
|
166
180
|
}
|
|
167
181
|
bindClientEvents() {
|
|
168
182
|
this.installTransportRpcObserver();
|
|
183
|
+
if (this.clientEventsBound.has(this.client)) {
|
|
184
|
+
this.syncBatchEventHandlers();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
this.clientEventsBound.add(this.client);
|
|
169
188
|
this.client.on('_raw.message.received', (data) => this.storeRawPushObservation('_raw.message.received', 'p2p', 'legacy', data));
|
|
170
189
|
this.client.on('_raw.peer.v2.message_received', (data) => this.storeRawPushObservation('_raw.peer.v2.message_received', 'p2p', 'v2', data));
|
|
171
190
|
this.client.on('_raw.group.message_created', (data) => this.storeRawPushObservation('_raw.group.message_created', 'group', 'legacy', data));
|
|
172
191
|
this.client.on('_raw.group.v2.message_created', (data) => this.storeRawPushObservation('_raw.group.v2.message_created', 'group', 'v2', data));
|
|
173
192
|
this.client.on('_raw.group.changed', (data) => this.storeRawGroupChangedObservation(data));
|
|
174
193
|
this.client.on('message.received', (msg) => {
|
|
175
|
-
|
|
194
|
+
this.singleEventCounts['message.received']++;
|
|
195
|
+
this.storeApplicationEvent('message.received', msg);
|
|
176
196
|
});
|
|
177
197
|
this.client.on('message.undecryptable', (msg) => {
|
|
178
198
|
void this.storeInboxItem(this.normalizeMessage(msg, false, 'undecryptable'));
|
|
179
199
|
});
|
|
180
200
|
this.client.on('group.message_created', (msg) => {
|
|
181
|
-
|
|
201
|
+
this.singleEventCounts['group.message_created']++;
|
|
202
|
+
this.storeApplicationEvent('group.message_created', msg);
|
|
182
203
|
});
|
|
183
204
|
this.client.on('group.message_undecryptable', (msg) => {
|
|
184
205
|
void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, false, 'undecryptable'));
|
|
185
206
|
});
|
|
186
|
-
this.client.on('group.changed', (data) =>
|
|
207
|
+
this.client.on('group.changed', (data) => {
|
|
208
|
+
this.singleEventCounts['group.changed']++;
|
|
209
|
+
this.storeApplicationEvent('group.changed', data);
|
|
210
|
+
});
|
|
187
211
|
this.client.on('group.invite_received', (data) => this.storeGroupInviteEvent(data));
|
|
188
212
|
for (const event of ['message.read_changed', 'group.read_changed', 'read_state_sync']) {
|
|
189
213
|
this.client.on(event, (payload) => { this.readStateEvents.push({ event, payload: jsonSafe(payload) }); });
|
|
190
214
|
}
|
|
215
|
+
this.client.on('message.peers_snapshot', (payload) => {
|
|
216
|
+
this.peersSnapshots.push({ event: 'message.peers_snapshot', payload: jsonSafe(payload) });
|
|
217
|
+
});
|
|
191
218
|
for (const event of ['ownership.request', 'ownership.action_required', 'ownership.progress', 'ownership.completed', 'ownership.rejected', 'ownership.stopped', 'ownership.error']) {
|
|
192
219
|
this.client.on(event, (payload) => { this.ownershipEvents.push({ event, payload: jsonSafe(payload) }); });
|
|
193
220
|
}
|
|
@@ -198,16 +225,89 @@ export class CrossSdkJsAgent {
|
|
|
198
225
|
this.recordTrace(textOf(payload.org_id), { stage: event, payload });
|
|
199
226
|
});
|
|
200
227
|
}
|
|
228
|
+
this.syncBatchEventHandlers();
|
|
229
|
+
}
|
|
230
|
+
syncBatchEventHandlers() {
|
|
231
|
+
const bound = this.batchEventHandlers.get(this.client);
|
|
232
|
+
if (!this.batchEventMode) {
|
|
233
|
+
if (bound)
|
|
234
|
+
for (const [event, handler] of bound)
|
|
235
|
+
this.client.off(event, handler);
|
|
236
|
+
this.batchEventHandlers.delete(this.client);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (bound)
|
|
240
|
+
return;
|
|
241
|
+
const handlers = new Map();
|
|
242
|
+
for (const event of ['message.batch', 'group.message_batch', 'group.event_batch']) {
|
|
243
|
+
const handler = (value) => {
|
|
244
|
+
const data = jsonObjectOf(value) ?? {};
|
|
245
|
+
const items = JSON.parse(stableStringify(jsonSafe(Array.isArray(data.items) ? data.items : [])));
|
|
246
|
+
this.batchEvents.push({ observation_id: this.rawPushObservationId, time_ms: Date.now(), event, namespace: textOf(data.namespace), source: textOf(data.source), items });
|
|
247
|
+
for (const item of items)
|
|
248
|
+
this.storeApplicationEvent(textOf(item.event), item.payload);
|
|
249
|
+
};
|
|
250
|
+
handlers.set(event, handler);
|
|
251
|
+
this.client.on(event, handler);
|
|
252
|
+
}
|
|
253
|
+
this.batchEventHandlers.set(this.client, handlers);
|
|
254
|
+
}
|
|
255
|
+
storeApplicationEvent(event, value) {
|
|
256
|
+
if (event === 'message.received') {
|
|
257
|
+
this.storePublicMessageEvent(event, 'p2p', value);
|
|
258
|
+
void this.storeInboxItem(this.normalizeMessage(value, true));
|
|
259
|
+
}
|
|
260
|
+
else if (event === 'group.message_created') {
|
|
261
|
+
this.storePublicMessageEvent(event, 'group', value);
|
|
262
|
+
void this.storeGroupInboxItem(this.normalizeGroupMessage(value, true));
|
|
263
|
+
}
|
|
264
|
+
else if (event === 'group.changed')
|
|
265
|
+
this.storeGroupChangedPublicEvent(value);
|
|
201
266
|
}
|
|
202
267
|
installTransportRpcObserver() {
|
|
268
|
+
const client = this.client;
|
|
269
|
+
const pipeline = client._rpcPipeline;
|
|
270
|
+
this.rpcObservedPipelines ??= new WeakSet();
|
|
271
|
+
this.rpcObservedPulls ??= new WeakMap();
|
|
272
|
+
if (pipeline?.bindActivePullCancellation && !this.rpcObservedPipelines.has(pipeline)) {
|
|
273
|
+
const originalBind = pipeline.bindActivePullCancellation;
|
|
274
|
+
this.rpcObservedPipelines.add(pipeline);
|
|
275
|
+
pipeline.bindActivePullCancellation = (method, params, request, gateKey = '') => {
|
|
276
|
+
const result = originalBind.call(pipeline, method, params, request, gateKey);
|
|
277
|
+
const observed = this.rpcObservedPulls.get(request);
|
|
278
|
+
if (!this.pullBarrier?.preserve || !observed || !['message.pull', 'message.v2.pull'].includes(method)
|
|
279
|
+
|| !textOf(params.sender_aid) || typeof client._delivery?.peerNamespace !== 'function')
|
|
280
|
+
return result;
|
|
281
|
+
const key = gateKey || pipeline.pullGateKeyForCall(method, params);
|
|
282
|
+
const namespace = client._delivery?.peerNamespace(textOf(params.sender_aid));
|
|
283
|
+
const owners = [...pipeline.pullGateStates.values()].flatMap((state) => [...state.activeByNamespace.values()])
|
|
284
|
+
.filter((job) => job.key === key && job.namespace === namespace && job.running && !job.timedOut && !job.invalidated && job.cancel);
|
|
285
|
+
if (owners.length === 1)
|
|
286
|
+
observed.owner = { job: owners[0], cancel: owners[0].cancel, key, namespace: namespace };
|
|
287
|
+
return result;
|
|
288
|
+
};
|
|
289
|
+
}
|
|
203
290
|
const transport = this.client._transport;
|
|
204
291
|
transport?.setTraceMode('diag');
|
|
205
292
|
if (!transport?.call || this.rpcObservedTransports.has(transport))
|
|
206
293
|
return;
|
|
207
294
|
const originalCall = transport.call;
|
|
295
|
+
const originalClose = transport.close;
|
|
208
296
|
this.rpcObservedTransports.add(transport);
|
|
209
|
-
|
|
297
|
+
if (originalClose)
|
|
298
|
+
transport.close = async () => {
|
|
299
|
+
if (!this.pullBarrier?.preserve)
|
|
300
|
+
this.releasePullBarrier();
|
|
301
|
+
return await originalClose.call(transport);
|
|
302
|
+
};
|
|
303
|
+
transport.call = (method, params, timeout, trace, background) => {
|
|
210
304
|
this.storeRawPushRpcMethod({ type: 'rpc', method, request_window: this.rawPushRequestWindow(params) });
|
|
305
|
+
const generation = this.observationGeneration;
|
|
306
|
+
const span = this.startRpcSpan(method, params);
|
|
307
|
+
const barrier = this.pullBarrier;
|
|
308
|
+
const controller = new AbortController();
|
|
309
|
+
let originalPromise;
|
|
310
|
+
const observed = {};
|
|
211
311
|
let requestParams = params;
|
|
212
312
|
if (method === 'auth.connect' && !this.inlineRealtimePayloadV1 && jsonObjectOf(params)) {
|
|
213
313
|
const request = { ...jsonObjectOf(params) };
|
|
@@ -218,9 +318,196 @@ export class CrossSdkJsAgent {
|
|
|
218
318
|
}
|
|
219
319
|
requestParams = request;
|
|
220
320
|
}
|
|
221
|
-
|
|
321
|
+
const observedPromise = (async () => {
|
|
322
|
+
try {
|
|
323
|
+
originalPromise = originalCall.call(transport, method, requestParams, timeout, trace, background);
|
|
324
|
+
const result = await originalPromise;
|
|
325
|
+
if (span && generation === this.observationGeneration)
|
|
326
|
+
this.finishRpcResponse(span, result);
|
|
327
|
+
const isPull = ['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.pull_events'].includes(method);
|
|
328
|
+
const isP2PPull = ['message.pull', 'message.v2.pull'].includes(method);
|
|
329
|
+
if (barrier && generation === this.observationGeneration && (isPull || barrier.method === method) && (barrier.scope !== 'p2p' || isP2PPull || barrier.method === method)
|
|
330
|
+
&& (!barrier.sender || barrier.sender === span?.sender_aid) && (!barrier.method || barrier.method === method)) {
|
|
331
|
+
const duration = (typeof timeout === 'number' && timeout > 0 ? timeout : transport._timeout ?? 35) * 1000;
|
|
332
|
+
await this.waitPullBarrier(barrier, controller.signal, Math.max(1, duration - (Date.now() - Number(span?.start_ms ?? Date.now()))), method, span, observed.owner);
|
|
333
|
+
}
|
|
334
|
+
if (span && generation === this.observationGeneration) {
|
|
335
|
+
this.injectRPCFailure(method, span);
|
|
336
|
+
span.success = true;
|
|
337
|
+
}
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
if (span && generation === this.observationGeneration) {
|
|
342
|
+
span.error = true;
|
|
343
|
+
span.success = false;
|
|
344
|
+
}
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
finally {
|
|
348
|
+
if (span && generation === this.observationGeneration)
|
|
349
|
+
span.end_ms = Date.now();
|
|
350
|
+
}
|
|
351
|
+
})();
|
|
352
|
+
observedPromise.cancel = () => { controller.abort(); originalPromise?.cancel?.(); };
|
|
353
|
+
this.rpcObservedPulls.set(observedPromise, observed);
|
|
354
|
+
return observedPromise;
|
|
222
355
|
};
|
|
223
356
|
}
|
|
357
|
+
storePublicMessageEvent(event, scope, value) {
|
|
358
|
+
const data = jsonObjectOf(value) ?? {};
|
|
359
|
+
const internal = this.client;
|
|
360
|
+
const payload = JSON.parse(stableStringify(jsonSafe(data.payload ?? null)));
|
|
361
|
+
this.eventLedger.push({
|
|
362
|
+
observation_id: this.rawPushObservationId, scope, event, event_index: this.eventLedger.length + 1,
|
|
363
|
+
time_ms: Date.now(), receiver_aid: this.aid, device_id: textOf(internal._deviceId), slot_id: textOf(internal._slotId || this.slotId),
|
|
364
|
+
sender_aid: textOf(data.from ?? data.from_aid ?? data.sender_aid), group_id: textOf(data.group_id),
|
|
365
|
+
message_id: textOf(data.message_id ?? data.id), seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
|
|
366
|
+
storage: textOf(data.storage), volatile_cursor: textOf(data.volatile_cursor),
|
|
367
|
+
payload, payload_sha256: sha256Json(payload), need_render: data.need_render !== false,
|
|
368
|
+
...(Object.prototype.hasOwnProperty.call(data, 'mention_mode') ? { mention_mode: data.mention_mode } : {}),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
startRpcSpan(method, value) {
|
|
372
|
+
if (!['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.events', 'group.pull_events', 'message.peers', 'message.ack', 'message.v2.ack', 'group.ack', 'group.v2.ack', 'group.ack_messages', 'group.ack_events'].includes(method))
|
|
373
|
+
return null;
|
|
374
|
+
const params = jsonObjectOf(value) ?? {};
|
|
375
|
+
const senderAid = textOf(params.sender_aid ?? params.peer_aid).trim();
|
|
376
|
+
const groupId = textOf(params.group_id ?? params.group_aid).trim();
|
|
377
|
+
const groupEvent = ['group.events', 'group.pull_events', 'group.ack_events'].includes(method);
|
|
378
|
+
const group = method.startsWith('group.');
|
|
379
|
+
const span = {
|
|
380
|
+
observation_id: this.rawPushObservationId, scope: group ? 'group' : 'p2p', span_id: this.rpcSpans.length + 1,
|
|
381
|
+
boundary: 'transport_call', method, start_ms: Date.now(), response_ms: null, end_ms: null, success: null,
|
|
382
|
+
cursor_scope: textOf(params.cursor_scope) || (group ? groupEvent ? 'group_event' : 'group' : senderAid || method === 'message.peers' ? 'peer' : 'global'),
|
|
383
|
+
sender_aid: senderAid, group_id: groupId,
|
|
384
|
+
namespace: group ? groupId ? `${groupEvent ? 'group_event' : 'group'}:${groupId}` : '' : method === 'message.peers' ? '' : this.syncNamespace('p2p', '', senderAid),
|
|
385
|
+
request_window: this.rpcSpanRequestWindow(params), response_message_count: 0, response_volatile_count: 0, response_event_count: 0, response_peer_count: 0, response_item_keys: [], response_event_keys: [],
|
|
386
|
+
has_more: null, has_more_persistent: null, has_more_volatile: null, persistent_has_more: null, volatile_has_more: null,
|
|
387
|
+
effective_ack_seq: null, peer_ack_seq: null, server_ack_seq: null, error: false,
|
|
388
|
+
};
|
|
389
|
+
this.rpcSpans.push(span);
|
|
390
|
+
return span;
|
|
391
|
+
}
|
|
392
|
+
rpcSpanRequestWindow(params) {
|
|
393
|
+
const out = this.rawPushRequestWindow(params);
|
|
394
|
+
for (const key of ['before_seq', 'until_seq', 'after_message_seq', 'after_event_seq', 'snapshot_head_seq', 'seq', 'up_to_seq', 'msg_seq', 'event_seq']) {
|
|
395
|
+
const value = params[key];
|
|
396
|
+
if (typeof value === 'number' && Number.isSafeInteger(value))
|
|
397
|
+
out[key] = value;
|
|
398
|
+
}
|
|
399
|
+
for (const key of ['after_volatile_cursor', 'volatile_cursor', 'mode', 'after_sender_aid', 'peer_mode', 'directory_mode'])
|
|
400
|
+
if (typeof params[key] === 'string')
|
|
401
|
+
out[key] = params[key];
|
|
402
|
+
for (const key of ['force', 'pending_only'])
|
|
403
|
+
if (typeof params[key] === 'boolean')
|
|
404
|
+
out[key] = params[key];
|
|
405
|
+
for (const key of ['volatile_cursors', 'volatile_ids'])
|
|
406
|
+
if (Array.isArray(params[key]))
|
|
407
|
+
out[key] = params[key].filter((item) => typeof item === 'string');
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
finishRpcResponse(span, value) {
|
|
411
|
+
const result = jsonObjectOf(value) ?? {};
|
|
412
|
+
span.response_ms = Date.now();
|
|
413
|
+
const messages = Array.isArray(result.messages) ? result.messages : [];
|
|
414
|
+
span.response_message_count = messages.length;
|
|
415
|
+
span.response_volatile_count = messages.filter((item) => jsonObjectOf(item)?.storage === 'volatile').length;
|
|
416
|
+
span.response_event_count = Array.isArray(result.events) ? result.events.length : 0;
|
|
417
|
+
span.response_peer_count = Array.isArray(result.peers) ? result.peers.length : 0;
|
|
418
|
+
const sequenceOf = (...values) => values.find((value) => typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value)) ?? 0;
|
|
419
|
+
const itemKeys = new Map();
|
|
420
|
+
for (const key of ['messages', 'volatile_messages', 'volatile'])
|
|
421
|
+
for (const value of Array.isArray(result[key]) ? result[key] : []) {
|
|
422
|
+
const item = jsonObjectOf(value) ?? {};
|
|
423
|
+
const cursor = textOf(item.volatile_cursor);
|
|
424
|
+
const identity = { message_id: textOf(item.message_id ?? item.id), seq: sequenceOf(item.seq, item.message_seq, item.msg_seq), storage: textOf(item.storage) || (cursor ? 'volatile' : 'persistent'), volatile_cursor: cursor };
|
|
425
|
+
itemKeys.set(stableStringify(identity), identity);
|
|
426
|
+
}
|
|
427
|
+
span.response_item_keys = [...itemKeys.values()];
|
|
428
|
+
span.response_event_keys = (Array.isArray(result.events) ? result.events : []).map((value) => {
|
|
429
|
+
const item = jsonObjectOf(value) ?? {};
|
|
430
|
+
return { event_id: textOf(item.event_id), event_seq: sequenceOf(item.event_seq, item.seq) };
|
|
431
|
+
});
|
|
432
|
+
span.has_more = typeof result.has_more === 'boolean' ? result.has_more : null;
|
|
433
|
+
span.has_more_persistent = typeof result.has_more_persistent === 'boolean' ? result.has_more_persistent : typeof result.persistent_has_more === 'boolean' ? result.persistent_has_more : null;
|
|
434
|
+
span.has_more_volatile = typeof result.has_more_volatile === 'boolean' ? result.has_more_volatile : typeof result.volatile_has_more === 'boolean' ? result.volatile_has_more : null;
|
|
435
|
+
span.persistent_has_more = typeof result.persistent_has_more === 'boolean' ? result.persistent_has_more : null;
|
|
436
|
+
span.volatile_has_more = typeof result.volatile_has_more === 'boolean' ? result.volatile_has_more : null;
|
|
437
|
+
const safeSeq = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
438
|
+
span.peer_ack_seq = safeSeq(result.peer_ack_seq);
|
|
439
|
+
span.server_ack_seq = safeSeq(result.server_ack_seq);
|
|
440
|
+
span.effective_ack_seq = safeSeq(result.peer_ack_seq ?? result.effective_ack_seq ?? result.ack_seq ?? result.server_ack_seq ?? jsonObjectOf(result.cursor)?.current_seq ?? result.cursor);
|
|
441
|
+
const volatileAck = jsonObjectOf(result.volatile_ack);
|
|
442
|
+
if (volatileAck) {
|
|
443
|
+
const ack = {};
|
|
444
|
+
for (const key of ['accepted', 'expired'])
|
|
445
|
+
if (typeof volatileAck[key] === 'number' && Number.isSafeInteger(volatileAck[key]))
|
|
446
|
+
ack[key] = volatileAck[key];
|
|
447
|
+
if (typeof volatileAck.storage === 'string')
|
|
448
|
+
ack.storage = volatileAck.storage;
|
|
449
|
+
span.volatile_ack = ack;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
releasePullBarrier() {
|
|
453
|
+
if (!this.pullBarrier || this.pullBarrier.released)
|
|
454
|
+
return;
|
|
455
|
+
this.pullBarrier.released = true;
|
|
456
|
+
this.pullBarrier.release();
|
|
457
|
+
}
|
|
458
|
+
injectRPCFailure(method, span) {
|
|
459
|
+
const failure = this.rpcFailures?.find((item) => !item.hit && item.method === method && item.sender_aid === span.sender_aid);
|
|
460
|
+
if (!failure)
|
|
461
|
+
return;
|
|
462
|
+
failure.hit = true;
|
|
463
|
+
span.injected_failure = failure.kind ?? 'error';
|
|
464
|
+
if (failure.kind === 'timeout')
|
|
465
|
+
throw new TimeoutError('cross-sdk 响应后一次超时');
|
|
466
|
+
throw new Error('cross-sdk 响应后一次失败');
|
|
467
|
+
}
|
|
468
|
+
resetObservationLedgers(count = 0, scope = 'all', controls = {}) {
|
|
469
|
+
this.releasePullBarrier();
|
|
470
|
+
this.observationGeneration++;
|
|
471
|
+
this.eventLedger = [];
|
|
472
|
+
this.rpcSpans = [];
|
|
473
|
+
this.batchEvents = [];
|
|
474
|
+
this.singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
|
|
475
|
+
this.rpcFailures = Array.isArray(controls.rpc_failures) ? controls.rpc_failures.map((item) => ({ ...jsonObjectOf(item) })) : [];
|
|
476
|
+
let release;
|
|
477
|
+
const promise = new Promise((resolve) => { release = resolve; });
|
|
478
|
+
this.pullBarrier = { target: count, admitted: 0, waiting: 0, released: count <= 0, scope: scope === 'p2p' ? 'p2p' : 'all', promise, release,
|
|
479
|
+
sender: textOf(controls.pull_barrier_sender_aid), method: textOf(controls.pull_barrier_method), preserve: controls.preserve_pull_barrier === true };
|
|
480
|
+
}
|
|
481
|
+
async waitPullBarrier(barrier, signal, timeout, method, span, owner) {
|
|
482
|
+
if (barrier.released || barrier.admitted >= barrier.target)
|
|
483
|
+
return;
|
|
484
|
+
if (signal.aborted && !barrier.preserve)
|
|
485
|
+
throw new Error('transport call cancelled');
|
|
486
|
+
barrier.admitted++;
|
|
487
|
+
barrier.waiting++;
|
|
488
|
+
if (barrier.preserve) {
|
|
489
|
+
barrier.heldMethod = method;
|
|
490
|
+
barrier.owner = owner;
|
|
491
|
+
barrier.span = span ?? undefined;
|
|
492
|
+
}
|
|
493
|
+
let timer;
|
|
494
|
+
let onAbort;
|
|
495
|
+
try {
|
|
496
|
+
await Promise.race([barrier.promise, new Promise((_resolve, reject) => {
|
|
497
|
+
onAbort = () => reject(new Error('transport call cancelled'));
|
|
498
|
+
if (!barrier.preserve)
|
|
499
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
500
|
+
timer = setTimeout(() => reject(new Error('pull barrier timed out')), timeout);
|
|
501
|
+
})]);
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
barrier.waiting--;
|
|
505
|
+
if (timer)
|
|
506
|
+
clearTimeout(timer);
|
|
507
|
+
if (onAbort)
|
|
508
|
+
signal.removeEventListener('abort', onAbort);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
224
511
|
storeRawPushObservation(rawEvent, scope, defaultKind, value) {
|
|
225
512
|
const data = jsonObjectOf(value) ?? {};
|
|
226
513
|
const hasInline = Object.prototype.hasOwnProperty.call(data, 'inline_message');
|
|
@@ -308,6 +595,7 @@ export class CrossSdkJsAgent {
|
|
|
308
595
|
};
|
|
309
596
|
}
|
|
310
597
|
async close() {
|
|
598
|
+
this.resetObservationLedgers();
|
|
311
599
|
try {
|
|
312
600
|
await this.client.close();
|
|
313
601
|
}
|
|
@@ -465,6 +753,7 @@ export class CrossSdkJsAgent {
|
|
|
465
753
|
decrypted,
|
|
466
754
|
encrypted: Boolean(data.e2ee ?? data.encrypted),
|
|
467
755
|
seq: Number(data.seq ?? data.message_seq ?? 0) || 0,
|
|
756
|
+
need_render: data.need_render !== false,
|
|
468
757
|
ack_seq: Number(data.ack_seq ?? 0) || 0,
|
|
469
758
|
storage: textOf(data.storage),
|
|
470
759
|
volatile_cursor: textOf(data.volatile_cursor),
|
|
@@ -489,6 +778,7 @@ export class CrossSdkJsAgent {
|
|
|
489
778
|
decrypted,
|
|
490
779
|
encrypted: Boolean(data.e2ee ?? data.encrypted),
|
|
491
780
|
seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
|
|
781
|
+
need_render: data.need_render !== false,
|
|
492
782
|
error_code: errorCode,
|
|
493
783
|
raw_sha256: sha256Json(data),
|
|
494
784
|
...envelopeMetadata(data),
|
|
@@ -611,6 +901,7 @@ export class CrossSdkJsAgent {
|
|
|
611
901
|
}
|
|
612
902
|
if (req.method === 'POST' && url.pathname === '/reset') {
|
|
613
903
|
const body = await readJson(req);
|
|
904
|
+
this.resetObservationLedgers();
|
|
614
905
|
const traceId = textOf(body.trace_id ?? '');
|
|
615
906
|
if (traceId) {
|
|
616
907
|
this.inbox = this.inbox.filter((item) => item.trace_id !== traceId);
|
|
@@ -621,6 +912,7 @@ export class CrossSdkJsAgent {
|
|
|
621
912
|
else {
|
|
622
913
|
this.inbox = [];
|
|
623
914
|
this.groupInbox = [];
|
|
915
|
+
this.peersSnapshots = [];
|
|
624
916
|
this.traces.clear();
|
|
625
917
|
this.sendResults.clear();
|
|
626
918
|
}
|
|
@@ -629,11 +921,25 @@ export class CrossSdkJsAgent {
|
|
|
629
921
|
}
|
|
630
922
|
if (req.method === 'POST' && url.pathname === '/test/raw-push/reset') {
|
|
631
923
|
const body = await readJson(req);
|
|
924
|
+
if ((body.pull_barrier_count !== undefined && (typeof body.pull_barrier_count !== 'number' || !Number.isSafeInteger(body.pull_barrier_count) || body.pull_barrier_count < 0)) ||
|
|
925
|
+
(body.pull_barrier_scope !== undefined && !['all', 'p2p'].includes(body.pull_barrier_scope)) ||
|
|
926
|
+
(body.observation_id !== undefined && typeof body.observation_id !== 'string') ||
|
|
927
|
+
(body.release_pull_barrier !== undefined && typeof body.release_pull_barrier !== 'boolean')) {
|
|
928
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid observation parameters' });
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (body.release_pull_barrier === true) {
|
|
932
|
+
this.releasePullBarrier();
|
|
933
|
+
sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
this.resetObservationLedgers(body.pull_barrier_count, textOf(body.pull_barrier_scope), body);
|
|
632
937
|
this.rawPushObservations = [];
|
|
633
938
|
this.rawPushRpcMethods = [];
|
|
634
939
|
this.groupChangedEvents = [];
|
|
635
940
|
this.groupInviteEvents = [];
|
|
636
941
|
this.readStateEvents = [];
|
|
942
|
+
this.peersSnapshots = [];
|
|
637
943
|
this.rawPushObservationId = textOf(body.observation_id ?? '');
|
|
638
944
|
sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
|
|
639
945
|
return;
|
|
@@ -652,6 +958,11 @@ export class CrossSdkJsAgent {
|
|
|
652
958
|
let rpcEntries = [...this.rawPushRpcMethods];
|
|
653
959
|
let groupChangedEvents = [...this.groupChangedEvents];
|
|
654
960
|
let groupInviteEvents = [...this.groupInviteEvents];
|
|
961
|
+
let eventLedger = [...this.eventLedger];
|
|
962
|
+
let rpcSpans = [...this.rpcSpans];
|
|
963
|
+
let batchEvents = [...this.batchEvents];
|
|
964
|
+
if (observationId)
|
|
965
|
+
batchEvents = batchEvents.filter((item) => textOf(item.observation_id) === observationId);
|
|
655
966
|
if (observationId)
|
|
656
967
|
rpcEntries = rpcEntries.filter((item) => textOf(item.observation_id) === observationId);
|
|
657
968
|
if (observationId)
|
|
@@ -662,17 +973,26 @@ export class CrossSdkJsAgent {
|
|
|
662
973
|
groupChangedEvents = groupChangedEvents.filter((item) => textOf(item.group_id) === filters.group_id);
|
|
663
974
|
if (filters.group_id)
|
|
664
975
|
groupInviteEvents = groupInviteEvents.filter((item) => textOf(item.group_id) === filters.group_id);
|
|
976
|
+
for (const key of ['observation_id', 'scope', 'group_id'])
|
|
977
|
+
if (filters[key]) {
|
|
978
|
+
eventLedger = eventLedger.filter((item) => textOf(item[key]) === filters[key]);
|
|
979
|
+
rpcSpans = rpcSpans.filter((item) => textOf(item[key]) === filters[key]);
|
|
980
|
+
}
|
|
665
981
|
rpcEntries = rpcEntries.slice(-limit);
|
|
666
982
|
const rpcMethods = rpcEntries.map((item) => textOf(item.method));
|
|
667
983
|
const rpcMethodCounts = {};
|
|
668
984
|
for (const method of rpcMethods)
|
|
669
985
|
rpcMethodCounts[method] = Number(rpcMethodCounts[method] ?? 0) + 1;
|
|
670
986
|
sendJson(res, 200, {
|
|
671
|
-
ok: true, items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
|
|
987
|
+
ok: true, time_ms: Date.now(), items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
|
|
672
988
|
rpc_calls: rpcEntries,
|
|
673
989
|
group_changed_events: groupChangedEvents.slice(-limit),
|
|
674
990
|
group_invite_events: groupInviteEvents.slice(-limit),
|
|
675
991
|
read_state_events: this.readStateEvents.slice(-limit),
|
|
992
|
+
peers_snapshots: this.peersSnapshots.slice(-limit),
|
|
993
|
+
event_ledger: eventLedger, rpc_spans: rpcSpans,
|
|
994
|
+
batch_event_mode: this.batchEventMode, batch_events: batchEvents, single_event_counts: this.singleEventCounts,
|
|
995
|
+
pull_barrier: { target: this.pullBarrier?.target ?? 0, waiting: this.pullBarrier?.waiting ?? 0, released: this.pullBarrier?.released ?? true, scope: this.pullBarrier?.scope ?? 'all' },
|
|
676
996
|
});
|
|
677
997
|
return;
|
|
678
998
|
}
|
|
@@ -1693,6 +2013,8 @@ export class CrossSdkJsAgent {
|
|
|
1693
2013
|
};
|
|
1694
2014
|
if (body.ttl !== undefined)
|
|
1695
2015
|
params.ttl = Number(body.ttl);
|
|
2016
|
+
if (body.need_render !== undefined)
|
|
2017
|
+
params.need_render = body.need_render;
|
|
1696
2018
|
const result = await this.client.call('message.send', params);
|
|
1697
2019
|
const resultObj = jsonObjectOf(result) ?? {};
|
|
1698
2020
|
const response = {
|
|
@@ -1727,6 +2049,10 @@ export class CrossSdkJsAgent {
|
|
|
1727
2049
|
const params = {};
|
|
1728
2050
|
if (seq > 0)
|
|
1729
2051
|
params.seq = seq;
|
|
2052
|
+
if (textOf(body.sender_aid).trim())
|
|
2053
|
+
params.sender_aid = textOf(body.sender_aid).trim();
|
|
2054
|
+
if (body.cursor_scope !== undefined)
|
|
2055
|
+
params.cursor_scope = body.cursor_scope;
|
|
1730
2056
|
if (Array.isArray(body.volatile_cursors))
|
|
1731
2057
|
params.volatile_cursors = body.volatile_cursors;
|
|
1732
2058
|
else if (textOf(body.volatile_cursor))
|
|
@@ -1748,9 +2074,26 @@ export class CrossSdkJsAgent {
|
|
|
1748
2074
|
}
|
|
1749
2075
|
async pull(req, res) {
|
|
1750
2076
|
const body = await readJson(req);
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
2077
|
+
if ((body.limit !== undefined && (typeof body.limit !== 'number' || !Number.isSafeInteger(body.limit) || body.limit < 1 || body.limit > 200)) ||
|
|
2078
|
+
(body.max_pages !== undefined && (typeof body.max_pages !== 'number' || !Number.isSafeInteger(body.max_pages) || body.max_pages < 1)) ||
|
|
2079
|
+
(body.after_seq !== undefined && (typeof body.after_seq !== 'number' || !Number.isSafeInteger(body.after_seq) || body.after_seq < 0)) ||
|
|
2080
|
+
['sender_aid', 'cursor_scope', 'after_volatile_cursor'].some((key) => body[key] !== undefined && typeof body[key] !== 'string') ||
|
|
2081
|
+
(body.force !== undefined && typeof body.force !== 'boolean') ||
|
|
2082
|
+
(body.window_mode !== undefined && !['forward', 'tail'].includes(body.window_mode))) {
|
|
2083
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid pull parameters' });
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
const senderAid = textOf(body.sender_aid).trim();
|
|
2087
|
+
const limit = body.limit ?? 50;
|
|
2088
|
+
const params = { limit };
|
|
2089
|
+
if (senderAid)
|
|
2090
|
+
params.sender_aid = senderAid;
|
|
2091
|
+
if (body.after_seq !== undefined)
|
|
2092
|
+
params.after_seq = body.after_seq;
|
|
2093
|
+
for (const key of ['cursor_scope', 'force', 'window_mode', 'max_pages']) {
|
|
2094
|
+
if (body[key] !== undefined)
|
|
2095
|
+
params[key] = body[key];
|
|
2096
|
+
}
|
|
1754
2097
|
if (textOf(body.after_volatile_cursor))
|
|
1755
2098
|
params.after_volatile_cursor = textOf(body.after_volatile_cursor);
|
|
1756
2099
|
try {
|
|
@@ -1771,6 +2114,20 @@ export class CrossSdkJsAgent {
|
|
|
1771
2114
|
}
|
|
1772
2115
|
async messageCall(req, res) {
|
|
1773
2116
|
const body = await readJson(req);
|
|
2117
|
+
if (['start', 'status', 'detach'].includes(textOf(body.action))) {
|
|
2118
|
+
this.messageOperations ??= new Map();
|
|
2119
|
+
const id = textOf(body.operation_id);
|
|
2120
|
+
if (body.action === 'start') {
|
|
2121
|
+
const operation = { ok: true, operation_id: id, status: 'running', started_ms: Date.now() };
|
|
2122
|
+
this.messageOperations.set(id, operation);
|
|
2123
|
+
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) }));
|
|
2124
|
+
}
|
|
2125
|
+
const operation = this.messageOperations.get(id);
|
|
2126
|
+
if (body.action === 'detach')
|
|
2127
|
+
Object.assign(operation, { detached: true, cancellation: 'application_wait_only', detached_ms: Date.now() });
|
|
2128
|
+
sendJson(res, 200, operation);
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
1774
2131
|
const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
|
|
1775
2132
|
const method = textOf(body.method).trim();
|
|
1776
2133
|
const params = jsonObjectOf(body.params) ?? {};
|
|
@@ -1802,15 +2159,15 @@ export class CrossSdkJsAgent {
|
|
|
1802
2159
|
sendJson(res, 500, error);
|
|
1803
2160
|
}
|
|
1804
2161
|
}
|
|
1805
|
-
syncNamespace(scope, groupId = '') {
|
|
2162
|
+
syncNamespace(scope, groupId = '', senderAid = '') {
|
|
1806
2163
|
if (scope === 'p2p')
|
|
1807
|
-
return `p2p:${this.aid}`;
|
|
2164
|
+
return senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
|
|
1808
2165
|
if (scope === 'group' && groupId)
|
|
1809
2166
|
return `group:${groupId}`;
|
|
1810
2167
|
throw new Error('scope must be p2p, or group with group_id');
|
|
1811
2168
|
}
|
|
1812
|
-
syncStateValue(scope, groupId = '') {
|
|
1813
|
-
const namespace = this.syncNamespace(scope, groupId);
|
|
2169
|
+
syncStateValue(scope, groupId = '', senderAid = '') {
|
|
2170
|
+
const namespace = this.syncNamespace(scope, groupId, senderAid);
|
|
1814
2171
|
const tracker = this.client._seqTracker;
|
|
1815
2172
|
if (!tracker)
|
|
1816
2173
|
throw new Error('SeqTracker is unavailable');
|
|
@@ -1819,9 +2176,10 @@ export class CrossSdkJsAgent {
|
|
|
1819
2176
|
syncState(url, res) {
|
|
1820
2177
|
const scope = textOf(url.searchParams.get('scope') || 'p2p').trim().toLowerCase();
|
|
1821
2178
|
const groupId = textOf(url.searchParams.get('group_id') || '').trim();
|
|
2179
|
+
const senderAid = textOf(url.searchParams.get('sender_aid') || '').trim();
|
|
1822
2180
|
try {
|
|
1823
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
1824
|
-
sendJson(res, 200, { ok: true, scope, ...snapshot });
|
|
2181
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
2182
|
+
sendJson(res, 200, { ok: true, scope, ...snapshot, peers_snapshots: this.peersSnapshots });
|
|
1825
2183
|
}
|
|
1826
2184
|
catch (err) {
|
|
1827
2185
|
sendJson(res, 400, {
|
|
@@ -1841,27 +2199,24 @@ export class CrossSdkJsAgent {
|
|
|
1841
2199
|
const body = await readJson(req);
|
|
1842
2200
|
const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
|
|
1843
2201
|
const groupId = textOf(body.group_id || '').trim();
|
|
2202
|
+
const senderAid = textOf(body.sender_aid || '').trim();
|
|
1844
2203
|
const afterSeq = Number(body.after_seq ?? 0) || 0;
|
|
1845
2204
|
const limit = Number(body.limit ?? 50) || 50;
|
|
1846
2205
|
try {
|
|
1847
|
-
const params = {
|
|
1848
|
-
if (
|
|
1849
|
-
params.
|
|
2206
|
+
const params = { limit, ...(tail ? { window_mode: 'tail' } : {}) };
|
|
2207
|
+
if (scope === 'group' || body.after_seq !== undefined)
|
|
2208
|
+
params.after_seq = afterSeq;
|
|
2209
|
+
if (scope === 'p2p' && senderAid)
|
|
2210
|
+
params.sender_aid = senderAid;
|
|
2211
|
+
for (const key of ['cursor_scope', 'force', 'max_pages', 'after_volatile_cursor']) {
|
|
2212
|
+
if (body[key] !== undefined)
|
|
2213
|
+
params[key] = body[key];
|
|
2214
|
+
}
|
|
1850
2215
|
const method = scope === 'group' ? 'group.pull' : 'message.pull';
|
|
1851
2216
|
if (scope === 'group')
|
|
1852
2217
|
params.group_id = groupId;
|
|
1853
|
-
this.syncNamespace(scope, groupId);
|
|
1854
|
-
|
|
1855
|
-
if (tail) {
|
|
1856
|
-
result = await this.client.call(method, params);
|
|
1857
|
-
}
|
|
1858
|
-
else {
|
|
1859
|
-
const transport = this.client._transport;
|
|
1860
|
-
if (!transport?.call)
|
|
1861
|
-
throw new Error('raw transport is unavailable');
|
|
1862
|
-
const wireMethod = scope === 'group' ? 'group.v2.pull' : 'message.v2.pull';
|
|
1863
|
-
result = await transport.call(wireMethod, params);
|
|
1864
|
-
}
|
|
2218
|
+
this.syncNamespace(scope, groupId, senderAid);
|
|
2219
|
+
const result = await this.client.call(method, params);
|
|
1865
2220
|
const resultObj = jsonObjectOf(result) ?? {};
|
|
1866
2221
|
const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
|
|
1867
2222
|
if (tail) {
|
|
@@ -1872,7 +2227,7 @@ export class CrossSdkJsAgent {
|
|
|
1872
2227
|
await this.storeInboxItem(this.normalizeMessage(msg, true));
|
|
1873
2228
|
}
|
|
1874
2229
|
}
|
|
1875
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
2230
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
1876
2231
|
sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
|
|
1877
2232
|
}
|
|
1878
2233
|
catch (err) {
|
|
@@ -1888,6 +2243,7 @@ export class CrossSdkJsAgent {
|
|
|
1888
2243
|
const body = await readJson(req);
|
|
1889
2244
|
const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
|
|
1890
2245
|
const groupId = textOf(body.group_id || '').trim();
|
|
2246
|
+
const senderAid = textOf(body.sender_aid || '').trim();
|
|
1891
2247
|
const beforeSeq = Number(body.before_seq ?? 0) || 0;
|
|
1892
2248
|
const limit = Number(body.limit ?? 50) || 50;
|
|
1893
2249
|
try {
|
|
@@ -1895,9 +2251,13 @@ export class CrossSdkJsAgent {
|
|
|
1895
2251
|
const method = scope === 'group' ? 'group.history' : 'message.history';
|
|
1896
2252
|
if (scope === 'group')
|
|
1897
2253
|
params.group_id = groupId;
|
|
1898
|
-
|
|
2254
|
+
if (scope === 'p2p' && senderAid)
|
|
2255
|
+
params.sender_aid = senderAid;
|
|
2256
|
+
if (body.cursor_scope !== undefined)
|
|
2257
|
+
params.cursor_scope = body.cursor_scope;
|
|
2258
|
+
this.syncNamespace(scope, groupId, senderAid);
|
|
1899
2259
|
const result = await this.client.call(method, params);
|
|
1900
|
-
const snapshot = this.syncStateValue(scope, groupId);
|
|
2260
|
+
const snapshot = this.syncStateValue(scope, groupId, senderAid);
|
|
1901
2261
|
sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
|
|
1902
2262
|
}
|
|
1903
2263
|
catch (err) {
|
|
@@ -1912,7 +2272,27 @@ export class CrossSdkJsAgent {
|
|
|
1912
2272
|
async sessionDisconnect(res) {
|
|
1913
2273
|
try {
|
|
1914
2274
|
this.ready = false;
|
|
1915
|
-
|
|
2275
|
+
if (!this.pullBarrier?.preserve)
|
|
2276
|
+
this.releasePullBarrier();
|
|
2277
|
+
const barrier = this.pullBarrier;
|
|
2278
|
+
let restore;
|
|
2279
|
+
if (barrier?.preserve && barrier.waiting === 1 && !barrier.released && ['message.pull', 'message.v2.pull'].includes(barrier.heldMethod ?? '')) {
|
|
2280
|
+
const binding = barrier.owner, pipeline = this.client._rpcPipeline;
|
|
2281
|
+
const owners = [...(pipeline?.pullGateStates.values() ?? [])].flatMap((state) => [...state.activeByNamespace.values()])
|
|
2282
|
+
.filter((job) => job === binding?.job && job.key === binding.key && job.namespace === binding.namespace && job.running && !job.timedOut && !job.invalidated && job.cancel === binding.cancel);
|
|
2283
|
+
if (owners.length !== 1)
|
|
2284
|
+
throw new Error('保留响应无法定位唯一受跟踪的 pull owner');
|
|
2285
|
+
const owner = owners[0], settled = owner.settled;
|
|
2286
|
+
owner.settled = Promise.resolve();
|
|
2287
|
+
restore = () => { owner.settled = settled; };
|
|
2288
|
+
barrier.span.barrier_owner_isolated = true;
|
|
2289
|
+
}
|
|
2290
|
+
try {
|
|
2291
|
+
await this.client.disconnect();
|
|
2292
|
+
}
|
|
2293
|
+
finally {
|
|
2294
|
+
restore?.();
|
|
2295
|
+
}
|
|
1916
2296
|
sendJson(res, 200, { ok: true, state: textOf(this.client.state) });
|
|
1917
2297
|
}
|
|
1918
2298
|
catch (err) {
|
|
@@ -1925,14 +2305,21 @@ export class CrossSdkJsAgent {
|
|
|
1925
2305
|
}
|
|
1926
2306
|
async sessionConnect(req, res) {
|
|
1927
2307
|
const body = await readJson(req);
|
|
2308
|
+
if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
|
|
2309
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
1928
2312
|
const backgroundSync = body.background_sync === true;
|
|
1929
2313
|
if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
|
|
1930
2314
|
this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
|
|
1931
2315
|
}
|
|
2316
|
+
if (typeof body.batch_events === 'boolean')
|
|
2317
|
+
this.batchEventMode = body.batch_events;
|
|
2318
|
+
this.syncBatchEventHandlers();
|
|
1932
2319
|
try {
|
|
1933
2320
|
await this.client.connect({ auto_reconnect: false, background_sync: backgroundSync });
|
|
1934
2321
|
this.ready = true;
|
|
1935
|
-
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
|
|
2322
|
+
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
|
|
1936
2323
|
}
|
|
1937
2324
|
catch (err) {
|
|
1938
2325
|
sendJson(res, 500, {
|
|
@@ -1944,19 +2331,27 @@ export class CrossSdkJsAgent {
|
|
|
1944
2331
|
}
|
|
1945
2332
|
async sessionRestart(req, res) {
|
|
1946
2333
|
const body = await readJson(req);
|
|
2334
|
+
if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
|
|
2335
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
1947
2338
|
const backgroundSync = body.background_sync === true;
|
|
1948
2339
|
if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
|
|
1949
2340
|
this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
|
|
1950
2341
|
}
|
|
2342
|
+
if (typeof body.batch_events === 'boolean')
|
|
2343
|
+
this.batchEventMode = body.batch_events;
|
|
2344
|
+
this.syncBatchEventHandlers();
|
|
1951
2345
|
this.ready = false;
|
|
1952
2346
|
try {
|
|
2347
|
+
this.releasePullBarrier();
|
|
1953
2348
|
await this.client.close();
|
|
1954
2349
|
this.client = this.newClient();
|
|
1955
2350
|
this.bindClientEvents();
|
|
1956
2351
|
await this.ensureConnected(backgroundSync);
|
|
1957
2352
|
this.startupError = '';
|
|
1958
2353
|
this.ready = true;
|
|
1959
|
-
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
|
|
2354
|
+
sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
|
|
1960
2355
|
}
|
|
1961
2356
|
catch (err) {
|
|
1962
2357
|
this.startupError = `${err instanceof Error ? err.name : 'Error'}: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -2070,6 +2465,7 @@ export class CrossSdkJsAgent {
|
|
|
2070
2465
|
payload: { type: 'text', text, trace_id: traceId, case_id: textOf(body.case_id || traceId) },
|
|
2071
2466
|
encrypt: e2ee,
|
|
2072
2467
|
message_id: messageId,
|
|
2468
|
+
...(body.need_render !== undefined ? { need_render: body.need_render } : {}),
|
|
2073
2469
|
});
|
|
2074
2470
|
const resultObj = jsonObjectOf(result) ?? {};
|
|
2075
2471
|
const response = {
|