@agentunion/fastaun 0.5.6 → 0.5.9
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 +106 -0
- package/_packed_docs/CHANGELOG.md +106 -0
- package/_packed_docs/INDEX.md +12 -11
- package/_packed_docs/KITE_DOCS_GUIDE.md +4 -3
- package/_packed_docs/agent.md/SCHEMA.md +83 -58
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
- package/_packed_docs/agent.md/examples//347/276/244/347/273/204-/345/274/200/345/217/221/345/233/242/351/230/237.md +21 -0
- package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +3 -3
- package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +7 -0
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +422 -331
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +598 -398
- package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
- package/_packed_docs/message-online-push-alignment.md +572 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +10 -10
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +30 -10
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +23 -20
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +5 -6
- package/_packed_docs/sdk/09-group-rpc-manual.md +11 -7
- package/_packed_docs/sdk/09-message-rpc-manual.md +5 -3
- package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +19 -17
- package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
- package/_packed_docs/sdk/INDEX.md +22 -22
- package/_packed_docs/sdk/README.md +6 -5
- package/dist/agent-md-schema.d.ts +16 -0
- package/dist/agent-md-schema.js +109 -0
- package/dist/agent-md-schema.js.map +1 -0
- package/dist/agent-md.d.ts +7 -0
- package/dist/agent-md.js +91 -9
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +5 -1
- package/dist/aid-store.js +25 -6
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +2 -0
- package/dist/aid.js +32 -12
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +3 -1
- package/dist/auth.js +65 -13
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +2 -1
- package/dist/cert-utils.js +32 -16
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +36 -7
- package/dist/client/delivery.js +701 -216
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +2 -2
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.d.ts +15 -1
- package/dist/client/lifecycle.js +475 -152
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +24 -6
- package/dist/client/rpc-pipeline.js +372 -131
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/runtime.d.ts +1 -3
- package/dist/client/runtime.js +4 -7
- package/dist/client/runtime.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +20 -3
- package/dist/client/v2-e2ee.js +376 -93
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +23 -10
- package/dist/client.js +515 -150
- package/dist/client.js.map +1 -1
- package/dist/discovery.d.ts +1 -0
- package/dist/discovery.js +3 -0
- package/dist/discovery.js.map +1 -1
- package/dist/facades.d.ts +23 -1
- package/dist/facades.js +59 -7
- package/dist/facades.js.map +1 -1
- package/dist/group-id.js +2 -17
- package/dist/group-id.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/net.d.ts +3 -0
- package/dist/net.js +13 -0
- package/dist/net.js.map +1 -1
- package/dist/seq-tracker.js +6 -1
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +29 -3
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.js +8 -1
- package/dist/transport.js.map +1 -1
- package/dist/validators.js +8 -14
- package/dist/validators.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as crypto from 'node:crypto';
|
|
2
2
|
import { normalizeSlotId, slotIsolationKey } from '../config.js';
|
|
3
|
-
import { ClientSignatureError, ConnectionError, PermissionError, ValidationError } from '../errors.js';
|
|
3
|
+
import { ClientSignatureError, ConnectionError, PermissionError, TimeoutError, ValidationError } from '../errors.js';
|
|
4
4
|
import { ConnectionState, isJsonObject, } from '../types.js';
|
|
5
5
|
import { validateAIDFormat, validateGroupIDFormat } from '../validators.js';
|
|
6
6
|
import { normalizeGroupMentionMode, normalizeGroupSettingsResponse } from './mention-mode.js';
|
|
@@ -71,6 +71,7 @@ const SIGNED_METHODS = new Set([
|
|
|
71
71
|
'group.dissolve', 'group.suspend', 'group.resume',
|
|
72
72
|
]);
|
|
73
73
|
const SIGNING_KEY_CACHE_MAX = 32;
|
|
74
|
+
const IDENTITY_ADMISSION_RETRY_DELAYS_MS = [0, 50, 100, 200];
|
|
74
75
|
const signingKeyCache = new Map();
|
|
75
76
|
const certFingerprintCache = new Map();
|
|
76
77
|
function cacheGet(cache, key) {
|
|
@@ -108,9 +109,17 @@ function getCertFingerprint(certPem) {
|
|
|
108
109
|
}
|
|
109
110
|
/** 旧式 tryAcquirePullGate 的防并发窗口。 */
|
|
110
111
|
const PULL_GATE_STALE_MS = 30000;
|
|
112
|
+
const PULL_GATE_OPERATION_TIMEOUT_MS = 3000;
|
|
113
|
+
function sameIdentityAdmissionRejection(error, original) {
|
|
114
|
+
const errorCode = Number(error?.code);
|
|
115
|
+
const originalCode = Number(original?.code);
|
|
116
|
+
const errorMessage = String(error instanceof Error ? error.message : error?.message ?? '').trim().toLowerCase();
|
|
117
|
+
const originalMessage = String(original instanceof Error ? original.message : original?.message ?? '').trim().toLowerCase();
|
|
118
|
+
return errorCode === -32003 && originalCode === -32003 && errorMessage === originalMessage;
|
|
119
|
+
}
|
|
111
120
|
const NON_IDEMPOTENT_TIMEOUT_MS = 35_000;
|
|
112
121
|
const NON_IDEMPOTENT_METHODS = new Set([
|
|
113
|
-
'message.send', 'group.send', 'group.create', 'group.invite',
|
|
122
|
+
'message.send', 'mail.send', 'group.send', 'group.create', 'group.invite',
|
|
114
123
|
'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
|
|
115
124
|
'group.set_settings',
|
|
116
125
|
'group.update_announcement', 'group.update_rules',
|
|
@@ -145,13 +154,19 @@ export class RpcPipeline {
|
|
|
145
154
|
runtime;
|
|
146
155
|
pullGateStates = new Map();
|
|
147
156
|
inlineRealtimeScopes = new Set();
|
|
148
|
-
|
|
157
|
+
pullGeneration = 0;
|
|
158
|
+
pullInvalidationWait = null;
|
|
149
159
|
constructor(runtime) {
|
|
150
160
|
this.runtime = runtime;
|
|
151
161
|
}
|
|
152
162
|
async call(method, params) {
|
|
153
163
|
const client = this.runtime.client;
|
|
154
164
|
const tStart = Date.now();
|
|
165
|
+
const requestedAckSeq = method === 'message.ack' || method === 'message.v2.ack'
|
|
166
|
+
? Number(params?.seq
|
|
167
|
+
?? params?.up_to_seq
|
|
168
|
+
?? 0) || 0
|
|
169
|
+
: 0;
|
|
155
170
|
client._clientLog.debug(`call enter: method=${method}`);
|
|
156
171
|
try {
|
|
157
172
|
const preflight = this.preflight(method, params);
|
|
@@ -166,6 +181,8 @@ export class RpcPipeline {
|
|
|
166
181
|
delete p._pull_gate_locked;
|
|
167
182
|
const skipSendResultEnvelope = Boolean(p._skip_send_result_envelope);
|
|
168
183
|
delete p._skip_send_result_envelope;
|
|
184
|
+
const deferGroupReadyPostprocess = Boolean(p._defer_group_ready_postprocess);
|
|
185
|
+
delete p._defer_group_ready_postprocess;
|
|
169
186
|
const pullGateKey = this.pullGateKeyForCall(method, p);
|
|
170
187
|
const runWithPullResponseScope = async (operation) => {
|
|
171
188
|
const scope = pullGateKey ? this.pullScopeKey(pullGateKey) : '';
|
|
@@ -174,12 +191,8 @@ export class RpcPipeline {
|
|
|
174
191
|
return await operation();
|
|
175
192
|
return await wrapper.call(client, scope, operation);
|
|
176
193
|
};
|
|
177
|
-
if (pullGateKey && this.isPullResponseProcessing(pullGateKey)) {
|
|
178
|
-
client._clientLog.debug(`pull skipped while processing pull response: method=${method} key=${pullGateKey}`);
|
|
179
|
-
return client._emptyPullResultForCall(method);
|
|
180
|
-
}
|
|
181
194
|
if (pullGateKey && !pullGateLocked) {
|
|
182
|
-
const pullBackground = this.pullGateBackgroundForCall(method, p);
|
|
195
|
+
const pullBackground = rpcBackground && this.pullGateBackgroundForCall(method, p);
|
|
183
196
|
const lockedParams = { ...p, _pull_gate_locked: true };
|
|
184
197
|
if (pullBackground)
|
|
185
198
|
lockedParams._rpc_background = true;
|
|
@@ -238,13 +251,10 @@ export class RpcPipeline {
|
|
|
238
251
|
if (p.window_mode === 'tail') {
|
|
239
252
|
return await runWithPullResponseScope(() => client._v2E2EE.pullV2TailInternal(p));
|
|
240
253
|
}
|
|
241
|
-
const skipAutoAck = p._skip_auto_ack === true || p.skip_auto_ack === true;
|
|
242
254
|
const force = p.force === true;
|
|
243
255
|
const afterSeq = Number(p.after_seq ?? 0) || 0;
|
|
244
256
|
const limit = Number(p.limit ?? 50) || 50;
|
|
245
|
-
const messages = await runWithPullResponseScope(() => (
|
|
246
|
-
? runWithRpcPriority(() => client._pullV2(afterSeq, limit, { skipAutoAck: true, gateLocked: true, force }))
|
|
247
|
-
: runWithRpcPriority(() => client._pullV2(afterSeq, limit, { gateLocked: true, force }))));
|
|
257
|
+
const messages = await runWithPullResponseScope(() => runWithRpcPriority(() => client._pullV2(afterSeq, limit, { gateLocked: true, force })));
|
|
248
258
|
return { messages };
|
|
249
259
|
}
|
|
250
260
|
if (method === 'message.history') {
|
|
@@ -253,7 +263,11 @@ export class RpcPipeline {
|
|
|
253
263
|
}
|
|
254
264
|
if (method === 'message.ack' || method === 'message.v2.ack') {
|
|
255
265
|
await client._ensureV2SessionReady('message.ack');
|
|
256
|
-
|
|
266
|
+
const ackSeq = requestedAckSeq || Number(p.seq ?? p.up_to_seq ?? 0) || 0;
|
|
267
|
+
if (ackSeq > 0 && client._aid) {
|
|
268
|
+
await client._delivery.waitForPendingInlineCommit(`p2p:${client._aid}`, ackSeq);
|
|
269
|
+
}
|
|
270
|
+
return await runWithRpcPriority(() => client._ackV2(ackSeq || undefined));
|
|
257
271
|
}
|
|
258
272
|
if (method === 'group.pull' || method === 'group.v2.pull') {
|
|
259
273
|
if (!String(p.group_id ?? '').trim()) {
|
|
@@ -264,8 +278,8 @@ export class RpcPipeline {
|
|
|
264
278
|
return await runWithPullResponseScope(() => client._v2E2EE.pullGroupV2TailInternal(p));
|
|
265
279
|
}
|
|
266
280
|
const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
|
|
267
|
-
const cursorParams =
|
|
268
|
-
const ownsCursor = Object.keys(cursorParams).length === 0 ||
|
|
281
|
+
const cursorParams = this.explicitGroupCursorParams(p);
|
|
282
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
|
|
269
283
|
const pullOpts = { gateLocked: true };
|
|
270
284
|
if (hasExplicitAfterSeq)
|
|
271
285
|
pullOpts.explicitAfterSeq = true;
|
|
@@ -294,8 +308,8 @@ export class RpcPipeline {
|
|
|
294
308
|
throw new ValidationError('group.ack_messages requires group_id');
|
|
295
309
|
}
|
|
296
310
|
await client._ensureV2SessionReady('group.ack_messages');
|
|
297
|
-
const cursorParams =
|
|
298
|
-
const ownsCursor = Object.keys(cursorParams).length === 0 ||
|
|
311
|
+
const cursorParams = this.explicitGroupCursorParams(p);
|
|
312
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
|
|
299
313
|
if (method === 'group.ack_messages' && !ownsCursor) {
|
|
300
314
|
return await runWithRpcPriority(() => client._rawGroupAckMessages(p));
|
|
301
315
|
}
|
|
@@ -311,15 +325,19 @@ export class RpcPipeline {
|
|
|
311
325
|
if (method === 'group.thought.get' || method === 'message.thought.get') {
|
|
312
326
|
client._clientLog.debug(`thought.get transport call start: method=${method}, params=${client._debugJson(client._messageEnvelopeFieldsForDebug(p))}`);
|
|
313
327
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
?
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
328
|
+
const invokeTransport = () => {
|
|
329
|
+
const request = callTimeout
|
|
330
|
+
? (rpcBackground
|
|
331
|
+
? client._transport.call(method, p, callTimeout, undefined, true)
|
|
332
|
+
: client._transport.call(method, p, callTimeout))
|
|
333
|
+
: (rpcBackground
|
|
334
|
+
? client._transport.call(method, p, undefined, undefined, true)
|
|
335
|
+
: client._transport.call(method, p));
|
|
336
|
+
return this.bindActivePullCancellation(method, p, request);
|
|
337
|
+
};
|
|
338
|
+
let result = await this.transportCallWithIdentityRecovery(invokeTransport, method, p);
|
|
321
339
|
const sendResultModeSource = result;
|
|
322
|
-
result = await this.postprocessResult(method, p, result);
|
|
340
|
+
result = await this.postprocessResult(method, p, result, { skipGroupState: deferGroupReadyPostprocess });
|
|
323
341
|
if (!skipSendResultEnvelope) {
|
|
324
342
|
result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted), sendResultModeSource);
|
|
325
343
|
}
|
|
@@ -531,9 +549,9 @@ export class RpcPipeline {
|
|
|
531
549
|
}
|
|
532
550
|
else {
|
|
533
551
|
mode = 'forward';
|
|
534
|
-
cursor = `after=${String(params.after_seq ?? 0)}
|
|
552
|
+
cursor = `after=${String(params.after_seq ?? 0)}`;
|
|
535
553
|
}
|
|
536
|
-
return `p2p:${client._aid}
|
|
554
|
+
return `p2p:${client._aid}|mode=${mode}|cursor=${cursor}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`;
|
|
537
555
|
}
|
|
538
556
|
if (method === 'group.pull' || method === 'group.v2.pull' || method === 'group.history') {
|
|
539
557
|
const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
|
|
@@ -551,13 +569,17 @@ export class RpcPipeline {
|
|
|
551
569
|
}
|
|
552
570
|
else {
|
|
553
571
|
mode = 'forward';
|
|
554
|
-
cursor = `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}
|
|
572
|
+
cursor = `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}`;
|
|
555
573
|
}
|
|
556
|
-
|
|
574
|
+
const explicitCursor = this.explicitGroupCursorParams(params);
|
|
575
|
+
const cursorSuffix = Object.keys(explicitCursor).length > 0
|
|
576
|
+
? `|cursor_params=${stableStringify(explicitCursor)}`
|
|
577
|
+
: '';
|
|
578
|
+
return `group:${groupId}|mode=${mode}|cursor=${cursor}${cursorSuffix}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`;
|
|
557
579
|
}
|
|
558
580
|
if (method === 'group.pull_events' && String(params.group_id ?? params.group_aid ?? '').trim()) {
|
|
559
581
|
const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
|
|
560
|
-
return `group_event:${groupId}|forward|after=${String(params.after_event_seq ?? 0)}|limit=${String(params.limit)}`;
|
|
582
|
+
return `group_event:${groupId}|mode=forward|cursor=after=${String(params.after_event_seq ?? 0)}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`;
|
|
561
583
|
}
|
|
562
584
|
return '';
|
|
563
585
|
}
|
|
@@ -570,23 +592,63 @@ export class RpcPipeline {
|
|
|
570
592
|
return true;
|
|
571
593
|
}
|
|
572
594
|
pullGateName(key) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
if (key.startsWith('group:'))
|
|
576
|
-
return 'group_message';
|
|
577
|
-
if (key.startsWith('group_event:'))
|
|
578
|
-
return 'group_event';
|
|
579
|
-
return `other:${key}`;
|
|
595
|
+
void key;
|
|
596
|
+
return 'pull';
|
|
580
597
|
}
|
|
581
598
|
pullGateState(key) {
|
|
582
599
|
const name = this.pullGateName(key);
|
|
583
600
|
let state = this.pullGateStates.get(name);
|
|
584
601
|
if (!state) {
|
|
585
|
-
state = { active: null, foreground: [], background: [], byKey: new Map() };
|
|
602
|
+
state = { active: null, foreground: [], background: [], byKey: new Map(), lifecycle: new Map(), watchdog: null };
|
|
586
603
|
this.pullGateStates.set(name, state);
|
|
587
604
|
}
|
|
605
|
+
this.startPullGateWatchdog(state, name);
|
|
588
606
|
return state;
|
|
589
607
|
}
|
|
608
|
+
pullGateOperationTimeoutMs() {
|
|
609
|
+
const client = this.runtime.client;
|
|
610
|
+
const configured = Number(client._PULL_GATE_OPERATION_TIMEOUT_MS
|
|
611
|
+
?? client._PULL_GATE_TIMEOUT_MS
|
|
612
|
+
?? client._PULL_GATE_QUEUE_TIMEOUT_MS);
|
|
613
|
+
return Number.isFinite(configured) && configured > 0 ? configured : PULL_GATE_OPERATION_TIMEOUT_MS;
|
|
614
|
+
}
|
|
615
|
+
startPullGateWatchdog(state, name) {
|
|
616
|
+
if (state.watchdog)
|
|
617
|
+
return;
|
|
618
|
+
const interval = Math.min(50, Math.max(1, Math.floor(this.pullGateOperationTimeoutMs() / 4)));
|
|
619
|
+
const timer = setInterval(() => this.checkPullGate(state, name), interval);
|
|
620
|
+
state.watchdog = timer;
|
|
621
|
+
const unref = timer.unref;
|
|
622
|
+
unref?.call(timer);
|
|
623
|
+
}
|
|
624
|
+
stopPullGateWatchdogs() {
|
|
625
|
+
for (const state of this.pullGateStates.values()) {
|
|
626
|
+
if (state.watchdog === null)
|
|
627
|
+
continue;
|
|
628
|
+
clearInterval(state.watchdog);
|
|
629
|
+
state.watchdog = null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
checkPullGate(state, name) {
|
|
633
|
+
const active = state.active;
|
|
634
|
+
if (!active) {
|
|
635
|
+
this.drainPullGate(state);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (!active.pullingStartedAt || Date.now() - active.pullingStartedAt < this.pullGateOperationTimeoutMs())
|
|
639
|
+
return;
|
|
640
|
+
if (active.timedOut)
|
|
641
|
+
return;
|
|
642
|
+
active.timedOut = true;
|
|
643
|
+
active.invalidated = true;
|
|
644
|
+
this.pullGeneration += 1;
|
|
645
|
+
active.cancel?.();
|
|
646
|
+
const timeout = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
|
|
647
|
+
this.runtime.client._clientLog.warn(`pull gate watchdog timeout: gate=${name} key=${active.key} elapsed=${Date.now() - active.pullingStartedAt}ms`);
|
|
648
|
+
// 仅拒绝当前调用方;取消不敏感的 operation 仍可能运行,必须等其
|
|
649
|
+
// 真正 settle 后才释放 active,避免与后续 Pull 并发修改游标。
|
|
650
|
+
active.reject(timeout);
|
|
651
|
+
}
|
|
590
652
|
pullScopeKey(key) {
|
|
591
653
|
return key.split('|', 1)[0] ?? key;
|
|
592
654
|
}
|
|
@@ -594,37 +656,37 @@ export class RpcPipeline {
|
|
|
594
656
|
const ns = String(namespace ?? '').trim();
|
|
595
657
|
return Boolean(ns) && this.inlineRealtimeScopes.has(ns);
|
|
596
658
|
}
|
|
597
|
-
|
|
598
|
-
const
|
|
599
|
-
this.
|
|
600
|
-
|
|
601
|
-
return
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
659
|
+
bindActivePullCancellation(method, params, request) {
|
|
660
|
+
const cancel = request.cancel;
|
|
661
|
+
const key = this.pullGateKeyForCall(method, params);
|
|
662
|
+
if (typeof cancel !== 'function' || !key)
|
|
663
|
+
return request;
|
|
664
|
+
const state = this.pullGateStates.get(this.pullGateName(key));
|
|
665
|
+
const active = state?.active;
|
|
666
|
+
if (!active || active.key !== key || active.timedOut || active.invalidated)
|
|
667
|
+
return request;
|
|
668
|
+
const boundCancel = () => cancel.call(request);
|
|
669
|
+
active.cancel = boundCancel;
|
|
670
|
+
return request.finally(() => {
|
|
671
|
+
if (active.cancel === boundCancel)
|
|
672
|
+
active.cancel = null;
|
|
673
|
+
});
|
|
612
674
|
}
|
|
613
675
|
isPullResponseProcessing(key) {
|
|
614
676
|
if (!key)
|
|
615
677
|
return false;
|
|
616
678
|
return (this.runtime.client._pullResponseKeys.get(this.pullScopeKey(key)) ?? 0) > 0;
|
|
617
679
|
}
|
|
618
|
-
hasPullActivity(namespace) {
|
|
680
|
+
hasPullActivity(namespace, includePending = true) {
|
|
619
681
|
const ns = String(namespace ?? '').trim();
|
|
620
682
|
if ((!ns.startsWith('p2p:') && !ns.startsWith('group:')) || this.hasInlineRealtimeGuard(ns))
|
|
621
683
|
return true;
|
|
622
684
|
const client = this.runtime.client;
|
|
623
685
|
if ((client._pullResponseKeys.get(ns) ?? 0) > 0)
|
|
624
686
|
return true;
|
|
625
|
-
if (Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0) > 0)
|
|
687
|
+
if (includePending && Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0) > 0)
|
|
626
688
|
return true;
|
|
627
|
-
if (
|
|
689
|
+
if (includePending && client._delivery?.hasPendingPull?.(ns))
|
|
628
690
|
return true;
|
|
629
691
|
for (const key of client._gapFillDone?.keys?.() ?? []) {
|
|
630
692
|
const marker = String(key ?? '').trim();
|
|
@@ -660,6 +722,7 @@ export class RpcPipeline {
|
|
|
660
722
|
const state = this.pullGateStates.get(this.pullGateName(ns));
|
|
661
723
|
if (state) {
|
|
662
724
|
this.drainPullGate(state);
|
|
725
|
+
this.runtime.client._delivery?.onPullGateIdle?.(ns);
|
|
663
726
|
this.armQueuedPullTimers(state);
|
|
664
727
|
}
|
|
665
728
|
}
|
|
@@ -694,94 +757,188 @@ export class RpcPipeline {
|
|
|
694
757
|
gate.inflight = false;
|
|
695
758
|
gate.startedAt = 0;
|
|
696
759
|
if (key.startsWith('p2p:')) {
|
|
697
|
-
client._schedulePendingP2pPullIfNeeded(key, 'pull-gate-release');
|
|
760
|
+
client._schedulePendingP2pPullIfNeeded?.(key, 'pull-gate-release');
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
isPullGenerationCurrent(generation) {
|
|
764
|
+
return generation === this.pullGeneration;
|
|
765
|
+
}
|
|
766
|
+
invalidatePulls() {
|
|
767
|
+
if (this.pullInvalidationWait)
|
|
768
|
+
return this.pullInvalidationWait;
|
|
769
|
+
this.pullGeneration += 1;
|
|
770
|
+
const error = new Error('pull invalidated');
|
|
771
|
+
const waits = [];
|
|
772
|
+
for (const state of this.pullGateStates.values()) {
|
|
773
|
+
const jobs = new Set();
|
|
774
|
+
if (state.active)
|
|
775
|
+
jobs.add(state.active);
|
|
776
|
+
for (const job of state.foreground)
|
|
777
|
+
jobs.add(job);
|
|
778
|
+
for (const job of state.background)
|
|
779
|
+
jobs.add(job);
|
|
780
|
+
for (const job of jobs) {
|
|
781
|
+
job.invalidated = true;
|
|
782
|
+
if (!job.timedOut)
|
|
783
|
+
job.reject(error);
|
|
784
|
+
job.cancel?.();
|
|
785
|
+
job.resumeResolve?.();
|
|
786
|
+
job.resumeResolve = null;
|
|
787
|
+
if (job.running) {
|
|
788
|
+
waits.push(job.settled);
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
job.settledResolve?.();
|
|
792
|
+
job.settledResolve = null;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
state.foreground = state.foreground.filter((job) => !jobs.has(job));
|
|
796
|
+
state.background = state.background.filter((job) => !jobs.has(job));
|
|
797
|
+
for (const [key, job] of state.byKey.entries()) {
|
|
798
|
+
if (jobs.has(job) && !job.running)
|
|
799
|
+
state.byKey.delete(key);
|
|
800
|
+
}
|
|
801
|
+
this.drainPullGate(state);
|
|
698
802
|
}
|
|
803
|
+
const wait = Promise.all(waits).then(() => undefined);
|
|
804
|
+
let tracked;
|
|
805
|
+
tracked = wait.finally(() => {
|
|
806
|
+
if (this.pullInvalidationWait === tracked)
|
|
807
|
+
this.pullInvalidationWait = null;
|
|
808
|
+
});
|
|
809
|
+
this.pullInvalidationWait = tracked;
|
|
810
|
+
return tracked;
|
|
811
|
+
}
|
|
812
|
+
pullInvalidationInProgress() {
|
|
813
|
+
return this.pullInvalidationWait !== null;
|
|
699
814
|
}
|
|
700
815
|
async runPullSerialized(key, operation, background = false) {
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
return [];
|
|
816
|
+
if (this.pullInvalidationInProgress()) {
|
|
817
|
+
throw new Error('pull invalidated');
|
|
704
818
|
}
|
|
705
819
|
if (!key)
|
|
706
820
|
return await this.executePullOperation(operation, background);
|
|
707
821
|
const state = this.pullGateState(key);
|
|
708
|
-
const
|
|
709
|
-
const
|
|
822
|
+
const namespace = this.pullScopeKey(key);
|
|
823
|
+
const active = state.active?.key === key && !state.active.timedOut && !state.active.invalidated ? state.active : null;
|
|
824
|
+
const queuedForeground = state.foreground.find((job) => job.key === key && !job.timedOut && !job.invalidated);
|
|
825
|
+
const queuedBackground = state.background.find((job) => job.key === key && !job.timedOut && !job.invalidated);
|
|
826
|
+
const existing = background
|
|
827
|
+
? (active ?? queuedForeground ?? queuedBackground)
|
|
828
|
+
: (queuedForeground ?? (active && !active.background ? active : null) ?? queuedBackground);
|
|
710
829
|
if (existing) {
|
|
830
|
+
if (!background && existing.background && existing !== state.active) {
|
|
831
|
+
const index = state.background.indexOf(existing);
|
|
832
|
+
if (index >= 0)
|
|
833
|
+
state.background.splice(index, 1);
|
|
834
|
+
existing.background = false;
|
|
835
|
+
state.foreground.push(existing);
|
|
836
|
+
}
|
|
837
|
+
const shared = existing.promise;
|
|
711
838
|
this.runtime.client._clientLog.debug(`pull gate coalesced: gate=${this.pullGateName(key)} key=${key} background=${String(background)}`);
|
|
712
|
-
|
|
713
|
-
return await existing.promise;
|
|
714
|
-
return await this.waitForCoalesced(existing.promise, timeoutMs, operation, background, key);
|
|
839
|
+
return await shared;
|
|
715
840
|
}
|
|
716
841
|
let resolve;
|
|
717
842
|
let reject;
|
|
718
843
|
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
|
844
|
+
let settledResolve;
|
|
845
|
+
const settled = new Promise((resolveSettled) => { settledResolve = resolveSettled; });
|
|
846
|
+
void promise.catch(() => { });
|
|
719
847
|
const job = {
|
|
720
848
|
key,
|
|
849
|
+
namespace,
|
|
721
850
|
background,
|
|
851
|
+
running: false,
|
|
852
|
+
suspended: false,
|
|
722
853
|
enqueuedAt: Date.now(),
|
|
723
854
|
operation,
|
|
724
855
|
promise,
|
|
725
856
|
resolve,
|
|
726
857
|
reject,
|
|
727
858
|
timer: null,
|
|
859
|
+
resume: null,
|
|
860
|
+
resumeResolve: null,
|
|
861
|
+
pullingStartedAt: 0,
|
|
862
|
+
timedOut: false,
|
|
863
|
+
invalidated: false,
|
|
864
|
+
cancel: null,
|
|
865
|
+
settled,
|
|
866
|
+
settledResolve,
|
|
728
867
|
};
|
|
729
868
|
const queue = background ? state.background : state.foreground;
|
|
730
869
|
queue.push(job);
|
|
731
870
|
state.byKey.set(key, job);
|
|
732
871
|
this.drainPullGate(state);
|
|
733
|
-
|
|
734
|
-
job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
|
|
735
|
-
}
|
|
872
|
+
this.checkPullGate(state, this.pullGateName(key));
|
|
736
873
|
return await promise;
|
|
737
874
|
}
|
|
738
875
|
async executePullOperation(operation, background) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (settled)
|
|
755
|
-
return;
|
|
756
|
-
settled = true;
|
|
757
|
-
clearTimeout(timer);
|
|
758
|
-
resolve(value);
|
|
759
|
-
}, (error) => {
|
|
760
|
-
if (settled)
|
|
761
|
-
return;
|
|
762
|
-
settled = true;
|
|
763
|
-
clearTimeout(timer);
|
|
764
|
-
reject(error);
|
|
765
|
-
});
|
|
766
|
-
});
|
|
876
|
+
const client = this.runtime.client;
|
|
877
|
+
const hadPrevious = Object.prototype.hasOwnProperty.call(client, '_pullOperationGeneration');
|
|
878
|
+
const previous = client._pullOperationGeneration;
|
|
879
|
+
client._pullOperationGeneration = this.pullGeneration;
|
|
880
|
+
try {
|
|
881
|
+
if (background)
|
|
882
|
+
return await client._withBackgroundRpc(operation);
|
|
883
|
+
return await operation();
|
|
884
|
+
}
|
|
885
|
+
finally {
|
|
886
|
+
if (hadPrevious)
|
|
887
|
+
client._pullOperationGeneration = previous;
|
|
888
|
+
else
|
|
889
|
+
delete client._pullOperationGeneration;
|
|
890
|
+
}
|
|
767
891
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
892
|
+
pullOperationIsCurrent() {
|
|
893
|
+
const generation = this.runtime.client._pullOperationGeneration;
|
|
894
|
+
return generation === undefined || this.isPullGenerationCurrent(generation);
|
|
895
|
+
}
|
|
896
|
+
throwIfPullInvalidated() {
|
|
897
|
+
if (!this.pullOperationIsCurrent())
|
|
898
|
+
throw new Error('pull invalidated');
|
|
899
|
+
}
|
|
900
|
+
async yieldPullGate(key, nextKey, background) {
|
|
901
|
+
if (!key)
|
|
773
902
|
return;
|
|
774
|
-
|
|
775
|
-
const
|
|
776
|
-
const
|
|
777
|
-
if (
|
|
903
|
+
const state = this.pullGateState(key);
|
|
904
|
+
const namespace = this.pullScopeKey(key);
|
|
905
|
+
const job = state.active?.key === key ? state.active : null;
|
|
906
|
+
if (!job || state.active !== job || job.timedOut || job.invalidated)
|
|
778
907
|
return;
|
|
779
|
-
|
|
908
|
+
const replacementKey = String(nextKey || key);
|
|
780
909
|
if (state.byKey.get(job.key) === job)
|
|
781
910
|
state.byKey.delete(job.key);
|
|
782
|
-
job.
|
|
783
|
-
|
|
784
|
-
|
|
911
|
+
job.key = replacementKey;
|
|
912
|
+
job.namespace = this.pullScopeKey(replacementKey);
|
|
913
|
+
if (background !== undefined)
|
|
914
|
+
job.background = background;
|
|
915
|
+
state.byKey.set(job.key, job);
|
|
916
|
+
state.lifecycle.set(namespace, 'pulled');
|
|
917
|
+
job.suspended = true;
|
|
918
|
+
job.resume = new Promise((resolve) => { job.resumeResolve = resolve; });
|
|
919
|
+
state.active = null;
|
|
920
|
+
(job.background ? state.background : state.foreground).push(job);
|
|
921
|
+
this.drainPullGate(state);
|
|
922
|
+
if (job.resume) {
|
|
923
|
+
await job.resume;
|
|
924
|
+
if (job.invalidated)
|
|
925
|
+
throw new Error('pull invalidated');
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
getPullLifecycle(namespace) {
|
|
929
|
+
const ns = this.pullScopeKey(namespace);
|
|
930
|
+
if (!ns)
|
|
931
|
+
return 'idle';
|
|
932
|
+
return this.pullGateStates.get(this.pullGateName(ns))?.lifecycle.get(ns) ?? 'idle';
|
|
933
|
+
}
|
|
934
|
+
setPullLifecycle(namespace, lifecycle) {
|
|
935
|
+
const ns = this.pullScopeKey(namespace);
|
|
936
|
+
if (!ns)
|
|
937
|
+
return;
|
|
938
|
+
const state = this.pullGateStates.get(this.pullGateName(ns));
|
|
939
|
+
if (!state || state.active?.namespace !== ns)
|
|
940
|
+
return;
|
|
941
|
+
state.lifecycle.set(ns, lifecycle);
|
|
785
942
|
}
|
|
786
943
|
drainPullGate(state) {
|
|
787
944
|
if (state.active)
|
|
@@ -798,24 +955,37 @@ export class RpcPipeline {
|
|
|
798
955
|
clearTimeout(job.timer);
|
|
799
956
|
job.timer = null;
|
|
800
957
|
}
|
|
801
|
-
|
|
802
|
-
|
|
958
|
+
job.pullingStartedAt = Date.now();
|
|
959
|
+
job.timedOut = false;
|
|
960
|
+
state.lifecycle.set(job.namespace, 'pulling');
|
|
961
|
+
const resumeResolve = job.resumeResolve;
|
|
962
|
+
job.resume = null;
|
|
963
|
+
job.resumeResolve = null;
|
|
964
|
+
if (job.suspended) {
|
|
965
|
+
job.suspended = false;
|
|
966
|
+
resumeResolve?.();
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
job.running = true;
|
|
970
|
+
const operationPromise = this.executePullOperation(job.operation, job.background);
|
|
971
|
+
void operationPromise
|
|
972
|
+
.then((value) => { if (!job.timedOut && !job.invalidated)
|
|
973
|
+
job.resolve(value); }, (error) => { if (!job.timedOut && !job.invalidated)
|
|
974
|
+
job.reject(error); })
|
|
803
975
|
.finally(() => {
|
|
976
|
+
state.lifecycle.set(job.namespace, 'idle');
|
|
804
977
|
if (state.active === job)
|
|
805
978
|
state.active = null;
|
|
806
979
|
if (state.byKey.get(job.key) === job)
|
|
807
980
|
state.byKey.delete(job.key);
|
|
808
981
|
this.drainPullGate(state);
|
|
982
|
+
job.settledResolve?.();
|
|
983
|
+
job.settledResolve = null;
|
|
984
|
+
if (!job.invalidated)
|
|
985
|
+
this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
|
|
809
986
|
});
|
|
810
987
|
}
|
|
811
|
-
armQueuedPullTimers(
|
|
812
|
-
const timeoutMs = Math.max(0, Number(this.runtime.client._PULL_GATE_QUEUE_TIMEOUT_MS ?? 3_000) || 0);
|
|
813
|
-
for (const job of [...state.foreground, ...state.background]) {
|
|
814
|
-
if (!job.timer && !this.hasInlineRealtimeGuard(this.pullScopeKey(job.key))) {
|
|
815
|
-
job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
988
|
+
armQueuedPullTimers(_state) { }
|
|
819
989
|
// ── raw-call ───────────────────────────────────────────────────────────────
|
|
820
990
|
async rawCall(method, params, opts = {}) {
|
|
821
991
|
const { timeout, trace, signed = true, background = false } = opts;
|
|
@@ -824,20 +994,50 @@ export class RpcPipeline {
|
|
|
824
994
|
if (signed) {
|
|
825
995
|
this.applyClientSignature(method, payload);
|
|
826
996
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
997
|
+
const invokeTransport = () => {
|
|
998
|
+
let request;
|
|
999
|
+
if (background)
|
|
1000
|
+
request = client._transport.call(method, payload, timeout, trace, true);
|
|
1001
|
+
else if (trace !== undefined)
|
|
1002
|
+
request = client._transport.call(method, payload, timeout, trace);
|
|
1003
|
+
else if (timeout !== undefined)
|
|
1004
|
+
request = client._transport.call(method, payload, timeout);
|
|
1005
|
+
else
|
|
1006
|
+
request = client._transport.call(method, payload);
|
|
1007
|
+
return this.bindActivePullCancellation(method, payload, request);
|
|
1008
|
+
};
|
|
1009
|
+
const result = await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
|
|
1010
|
+
this.throwIfPullInvalidated();
|
|
1011
|
+
return result;
|
|
1012
|
+
}
|
|
1013
|
+
async transportCallWithIdentityRecovery(operation, method, params) {
|
|
1014
|
+
try {
|
|
1015
|
+
return await operation();
|
|
832
1016
|
}
|
|
833
|
-
|
|
834
|
-
|
|
1017
|
+
catch (err) {
|
|
1018
|
+
const recover = this.runtime.client._recoverIdentityAdmission;
|
|
1019
|
+
if (typeof recover !== 'function' || !await recover.call(this.runtime.client, err, method, params))
|
|
1020
|
+
throw err;
|
|
1021
|
+
let lastError = err;
|
|
1022
|
+
for (const delay of IDENTITY_ADMISSION_RETRY_DELAYS_MS) {
|
|
1023
|
+
if (delay > 0)
|
|
1024
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1025
|
+
try {
|
|
1026
|
+
return await operation();
|
|
1027
|
+
}
|
|
1028
|
+
catch (retryError) {
|
|
1029
|
+
if (!sameIdentityAdmissionRejection(retryError, err))
|
|
1030
|
+
throw retryError;
|
|
1031
|
+
lastError = retryError;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
throw lastError;
|
|
835
1035
|
}
|
|
836
|
-
return await client._transport.call(method, payload);
|
|
837
1036
|
}
|
|
838
1037
|
// ── postprocess ────────────────────────────────────────────────────────────
|
|
839
|
-
async postprocessResult(method, params, result) {
|
|
1038
|
+
async postprocessResult(method, params, result, options = {}) {
|
|
840
1039
|
const client = this.runtime.client;
|
|
1040
|
+
this.throwIfPullInvalidated();
|
|
841
1041
|
let next = result;
|
|
842
1042
|
if ((method === 'group.send' || method === 'group.v2.send') && isUnknownJsonObject(next)) {
|
|
843
1043
|
next = normalizeGroupMentionMode(next);
|
|
@@ -854,12 +1054,40 @@ export class RpcPipeline {
|
|
|
854
1054
|
next = await client._decryptMessageThoughts(next);
|
|
855
1055
|
}
|
|
856
1056
|
if (method === 'message.pull' && isUnknownJsonObject(next)) {
|
|
857
|
-
|
|
1057
|
+
const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
|
|
1058
|
+
if (!isTail && client._aid && client._seqTracker) {
|
|
1059
|
+
const ns = `p2p:${client._aid}`;
|
|
1060
|
+
const afterSeq = Math.max(0, Number(params.after_seq ?? 0) || 0);
|
|
1061
|
+
await client._delivery.commitPlainForwardPage(ns, afterSeq, 'message.pull', next);
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
this.postprocessMessagePull(params, next);
|
|
1065
|
+
}
|
|
858
1066
|
}
|
|
859
1067
|
if (method === 'group.pull' && isUnknownJsonObject(next)) {
|
|
860
|
-
|
|
1068
|
+
if (Array.isArray(next.messages)) {
|
|
1069
|
+
next.messages = next.messages.map((message) => normalizeGroupMentionMode(message));
|
|
1070
|
+
}
|
|
1071
|
+
const gid = String(params.group_id ?? params.group_aid ?? '').trim();
|
|
1072
|
+
const explicitCursorParams = this.explicitGroupCursorParams(params);
|
|
1073
|
+
const ownsCursor = Object.keys(explicitCursorParams).length === 0
|
|
1074
|
+
|| this.groupCursorTargetsCurrentInstance(explicitCursorParams);
|
|
1075
|
+
const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
|
|
1076
|
+
if (!ownsCursor) {
|
|
1077
|
+
client._clientLog.debug(`group.pull external cursor skips local Forward commit: group=${gid}`);
|
|
1078
|
+
}
|
|
1079
|
+
else if (!isTail && gid && client._seqTracker) {
|
|
1080
|
+
const ns = `group:${gid}`;
|
|
1081
|
+
const afterSeq = Math.max(0, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
|
|
1082
|
+
await client._delivery.commitPlainForwardPage(ns, afterSeq, 'group.pull', next, gid);
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
this.postprocessGroupPull(params, next);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (!options.skipGroupState) {
|
|
1089
|
+
next = await client._groupState.postprocessResult(method, params, next);
|
|
861
1090
|
}
|
|
862
|
-
next = await client._groupState.postprocessResult(method, params, next);
|
|
863
1091
|
return next;
|
|
864
1092
|
}
|
|
865
1093
|
// ── private helpers ────────────────────────────────────────────────────────
|
|
@@ -973,6 +1201,19 @@ export class RpcPipeline {
|
|
|
973
1201
|
}
|
|
974
1202
|
return cursorParams;
|
|
975
1203
|
}
|
|
1204
|
+
explicitGroupCursorParams(params) {
|
|
1205
|
+
const value = params._group_cursor_params;
|
|
1206
|
+
if (!isJsonObject(value))
|
|
1207
|
+
return {};
|
|
1208
|
+
return { ...value };
|
|
1209
|
+
}
|
|
1210
|
+
groupCursorTargetsCurrentInstance(params) {
|
|
1211
|
+
const client = this.runtime.client;
|
|
1212
|
+
const deviceId = String(params.device_id ?? '').trim();
|
|
1213
|
+
const slotId = String(params.slot_id ?? '').trim();
|
|
1214
|
+
return (!deviceId || deviceId === (client._deviceId ?? ''))
|
|
1215
|
+
&& (!slotId || slotId === (client._slotId ?? ''));
|
|
1216
|
+
}
|
|
976
1217
|
validateMessageRecipient(toAid) {
|
|
977
1218
|
if (!String(toAid ?? '').trim()) {
|
|
978
1219
|
throw new ValidationError("message.send requires 'to' (recipient AID)");
|