@agentunion/fastaun 0.5.30 → 0.5.32

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/_packed_docs/CHANGELOG.md +60 -0
  3. package/_packed_docs/sdk/0.5.31-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +162 -0
  4. package/_packed_docs/sdk/0.5.31-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +167 -0
  5. package/_packed_docs/sdk/0.5.32-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +350 -0
  6. package/_packed_docs/sdk/0.5.32-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +319 -0
  7. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +12 -5
  8. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +459 -0
  9. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/274/200/345/217/221/347/247/273/346/244/215/346/214/207/345/215/227.md +554 -0
  10. package/_packed_docs/sdk/INDEX.md +27 -5
  11. package/_packed_docs/sdk//345/255/220AID/344/277/256/346/224/271agent.md/351/200/232/347/237/245Owner/345/234/272/346/231/257/346/214/207/345/215/227.md +234 -0
  12. package/dist/agent-md-schema.js +7 -6
  13. package/dist/agent-md-schema.js.map +1 -1
  14. package/dist/agent-md.d.ts +2 -0
  15. package/dist/agent-md.js +20 -6
  16. package/dist/agent-md.js.map +1 -1
  17. package/dist/aid-store.d.ts +1 -0
  18. package/dist/aid-store.js +1 -0
  19. package/dist/aid-store.js.map +1 -1
  20. package/dist/aid.js +1 -1
  21. package/dist/aid.js.map +1 -1
  22. package/dist/auth.js +1 -1
  23. package/dist/auth.js.map +1 -1
  24. package/dist/client/control-messages.d.ts +22 -7
  25. package/dist/client/control-messages.js +220 -185
  26. package/dist/client/control-messages.js.map +1 -1
  27. package/dist/client/delivery.d.ts +3 -0
  28. package/dist/client/delivery.js +67 -3
  29. package/dist/client/delivery.js.map +1 -1
  30. package/dist/client/group-state.d.ts +3 -0
  31. package/dist/client/group-state.js +48 -14
  32. package/dist/client/group-state.js.map +1 -1
  33. package/dist/client/lifecycle.js +12 -3
  34. package/dist/client/lifecycle.js.map +1 -1
  35. package/dist/client/rpc-pipeline.d.ts +9 -3
  36. package/dist/client/rpc-pipeline.js +61 -26
  37. package/dist/client/rpc-pipeline.js.map +1 -1
  38. package/dist/client/v2-e2ee.d.ts +13 -1
  39. package/dist/client/v2-e2ee.js +32 -19
  40. package/dist/client/v2-e2ee.js.map +1 -1
  41. package/dist/client.d.ts +18 -1
  42. package/dist/client.js +396 -140
  43. package/dist/client.js.map +1 -1
  44. package/dist/discovery.d.ts +1 -0
  45. package/dist/discovery.js +3 -1
  46. package/dist/discovery.js.map +1 -1
  47. package/dist/group-fs.js +1 -1
  48. package/dist/group-fs.js.map +1 -1
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/ownership-v21.d.ts +2 -2
  53. package/dist/ownership-v21.js +18 -21
  54. package/dist/ownership-v21.js.map +1 -1
  55. package/dist/ownership-workflow.js +6 -4
  56. package/dist/ownership-workflow.js.map +1 -1
  57. package/dist/pubsub/binding.d.ts +54 -0
  58. package/dist/pubsub/binding.js +343 -0
  59. package/dist/pubsub/binding.js.map +1 -0
  60. package/dist/pubsub/channel.d.ts +45 -0
  61. package/dist/pubsub/channel.js +137 -0
  62. package/dist/pubsub/channel.js.map +1 -0
  63. package/dist/pubsub/client.d.ts +93 -0
  64. package/dist/pubsub/client.js +318 -0
  65. package/dist/pubsub/client.js.map +1 -0
  66. package/dist/pubsub/credentials.d.ts +51 -0
  67. package/dist/pubsub/credentials.js +360 -0
  68. package/dist/pubsub/credentials.js.map +1 -0
  69. package/dist/pubsub/errors.d.ts +5 -0
  70. package/dist/pubsub/errors.js +6 -0
  71. package/dist/pubsub/errors.js.map +1 -0
  72. package/dist/pubsub/index.d.ts +48 -0
  73. package/dist/pubsub/index.js +152 -0
  74. package/dist/pubsub/index.js.map +1 -0
  75. package/dist/pubsub/scheduler.d.ts +58 -0
  76. package/dist/pubsub/scheduler.js +249 -0
  77. package/dist/pubsub/scheduler.js.map +1 -0
  78. package/dist/pubsub/service.d.ts +100 -0
  79. package/dist/pubsub/service.js +650 -0
  80. package/dist/pubsub/service.js.map +1 -0
  81. package/dist/pubsub/types.d.ts +119 -0
  82. package/dist/pubsub/types.js +2 -0
  83. package/dist/pubsub/types.js.map +1 -0
  84. package/dist/register-flow.js +1 -1
  85. package/dist/register-flow.js.map +1 -1
  86. package/dist/storage/lowlevel.js +49 -8
  87. package/dist/storage/lowlevel.js.map +1 -1
  88. package/dist/storage/read-cache.d.ts +7 -3
  89. package/dist/storage/read-cache.js +137 -18
  90. package/dist/storage/read-cache.js.map +1 -1
  91. package/dist/storage/vfs.d.ts +1 -0
  92. package/dist/storage/vfs.js +12 -4
  93. package/dist/storage/vfs.js.map +1 -1
  94. package/dist/tools/cross-sdk-agent.d.ts +1 -1
  95. package/dist/tools/cross-sdk-agent.js +4 -2
  96. package/dist/tools/cross-sdk-agent.js.map +1 -1
  97. package/dist/tools/pubsub-actor.d.ts +27 -0
  98. package/dist/tools/pubsub-actor.js +260 -0
  99. package/dist/tools/pubsub-actor.js.map +1 -0
  100. package/dist/transport.d.ts +26 -2
  101. package/dist/transport.js +182 -17
  102. package/dist/transport.js.map +1 -1
  103. package/dist/version.d.ts +1 -1
  104. package/dist/version.js +1 -1
  105. package/package.json +1 -1
@@ -5,15 +5,6 @@ export const CLASS_RECEIVE_METHODS = new Set([
5
5
  'group.pull', 'group.v2.pull', 'group.history', 'group.ack', 'group.v2.ack', 'group.ack_messages',
6
6
  ]);
7
7
  function object(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
8
- function groupStreamNeedsPull(entry, messageClass, storage) {
9
- if (!object(entry) || !object(entry.streams))
10
- return true;
11
- const classState = entry.streams[messageClass];
12
- if (!object(classState))
13
- return false;
14
- const streamState = classState[storage];
15
- return object(streamState) && streamState.needs_pull === true;
16
- }
17
8
  function protocolVersion(value) {
18
9
  const version = typeof value === 'string' && /^[1-9][0-9]*$/.test(value) ? Number(value) : value;
19
10
  return Number.isSafeInteger(version) && Number(version) > 0 ? Number(version) : null;
@@ -60,16 +51,156 @@ export function applicationResult(value) {
60
51
  export class ControlMessageCoordinator {
61
52
  client;
62
53
  locks = new Map();
63
- pending = new Map();
64
- workers = new Map();
65
54
  epoch = 0;
55
+ pubsubPages = 0;
56
+ pubsubPageWaiters = [];
57
+ pubsubReceivers = new Map();
58
+ pubsubCandidates = [];
59
+ pubsubBytes = 0;
60
+ blockedPubsubPulls = new Map();
61
+ pubsubWorkers = new Set();
66
62
  constructor(client) {
67
63
  this.client = client;
68
64
  }
65
+ async acquirePubsubPage(identity) {
66
+ if (this.pubsubPages < 4)
67
+ this.pubsubPages++;
68
+ else
69
+ await new Promise((resolve, reject) => { this.pubsubPageWaiters.push({ resolve, reject }); });
70
+ try {
71
+ this.check(identity);
72
+ }
73
+ catch (error) {
74
+ this.releasePubsubPage();
75
+ throw error;
76
+ }
77
+ return () => this.releasePubsubPage();
78
+ }
79
+ releasePubsubPage() {
80
+ const waiter = this.pubsubPageWaiters.shift();
81
+ if (waiter)
82
+ waiter.resolve();
83
+ else
84
+ this.pubsubPages--;
85
+ }
69
86
  supports(messageClass, group = false) {
70
- const classes = this.client._negotiatedCapabilities?.message_classes_v1?.[group ? 'group' : 'p2p'];
87
+ if (group && messageClass === 'sdk_control')
88
+ return false;
89
+ const classes = this.client._negotiatedCapabilities?.message_classes_v2?.[group ? 'group' : 'p2p'];
71
90
  return Array.isArray(classes) && classes.includes(messageClass);
72
91
  }
92
+ registerPubsub(receiver, options) {
93
+ if (this.pubsubReceivers.size >= 64)
94
+ throw new StateError('control pubsub receivers are full');
95
+ const token = {};
96
+ this.pubsubReceivers.set(token, { receiver, ...options, identity: this.pubsubIdentity() });
97
+ return () => {
98
+ this.pubsubReceivers.delete(token);
99
+ if (!this.pubsubReceivers.size) {
100
+ this.pubsubCandidates = [];
101
+ this.pubsubBytes = 0;
102
+ this.blockedPubsubPulls.clear();
103
+ }
104
+ };
105
+ }
106
+ pubsubIdentity() {
107
+ const c = this.client;
108
+ return JSON.stringify([c._aid, c._deviceId, c._slotId]);
109
+ }
110
+ queuePubsubCandidate(data) {
111
+ if (!this.supports('sdk_control') || ![...this.pubsubReceivers.values()].some(value => value.identity === this.pubsubIdentity()))
112
+ return;
113
+ const row = object(data.inline_message) ? { ...data, ...data.inline_message } : data;
114
+ if (!row.volatile_cursor)
115
+ return;
116
+ let envelope = row.envelope ?? row.payload;
117
+ if (typeof row.envelope_json === 'string') {
118
+ try {
119
+ envelope = JSON.parse(row.envelope_json);
120
+ }
121
+ catch {
122
+ return;
123
+ }
124
+ }
125
+ if (!object(envelope) || envelope.type !== 'e2ee.p2p_encrypted' || envelope.version !== 'v2')
126
+ return;
127
+ const bytes = new TextEncoder().encode(JSON.stringify(row)).byteLength;
128
+ if (this.pubsubCandidates.length >= 64 || this.pubsubBytes + bytes > 1048576)
129
+ return;
130
+ const receivedAt = Date.now();
131
+ const deadline = Number.isSafeInteger(row.expires_at) ? row.expires_at : Number.isFinite(row.t_server) && Number.isFinite(row.ttl) ? row.t_server + row.ttl * 1000 : receivedAt + 60_000;
132
+ if (deadline <= receivedAt)
133
+ return;
134
+ this.pubsubCandidates.push({ row: structuredClone({ ...row, from_aid: row.from_aid ?? row.from ?? row.sender_aid, envelope_json: JSON.stringify(envelope) }), identity: this.identity(), tokens: [...this.pubsubReceivers.keys()], bytes, deadline });
135
+ this.pubsubBytes += bytes;
136
+ this.wakePubsubCandidates();
137
+ }
138
+ wakePubsubCandidates() {
139
+ while (this.pubsubWorkers.size < 2 && this.pubsubCandidates.length) {
140
+ const task = Promise.resolve().then(() => this.drainPubsubCandidates());
141
+ this.pubsubWorkers.add(task);
142
+ void task.finally(() => { this.pubsubWorkers.delete(task); this.wakePubsubCandidates(); });
143
+ }
144
+ }
145
+ async drainPubsubCandidates() {
146
+ while (this.pubsubCandidates.length) {
147
+ const candidate = this.pubsubCandidates.shift();
148
+ this.pubsubBytes -= candidate.bytes;
149
+ try {
150
+ this.check(candidate.identity);
151
+ if (candidate.deadline <= Date.now())
152
+ continue;
153
+ let timer;
154
+ let decoded;
155
+ try {
156
+ decoded = await Promise.race([
157
+ this.decode(candidate.row, 'sdk_control', false, {}, { background: 'event', deadline: candidate.deadline }),
158
+ new Promise((_, reject) => { timer = setTimeout(() => reject(new StateError('control pubsub candidate expired')), candidate.deadline - Date.now()); }),
159
+ ]);
160
+ }
161
+ finally {
162
+ if (timer !== undefined)
163
+ clearTimeout(timer);
164
+ }
165
+ if (candidate.deadline <= Date.now())
166
+ continue;
167
+ this.check(candidate.identity);
168
+ this.dispatchPubsub(decoded, candidate.tokens);
169
+ }
170
+ catch { }
171
+ }
172
+ }
173
+ dispatchPubsub(message, tokens = this.pubsubReceivers.keys()) {
174
+ const headers = message.protected_headers;
175
+ if (!object(headers) || headers.message_class !== 'sdk_control' || headers.sdk_control_protocol !== 'aun.pubsub' || protocolVersion(headers.sdk_control_version) !== 1)
176
+ return true;
177
+ const payload = message.payload, sender = message.from ?? message.from_aid ?? message.sender_aid;
178
+ if (message.encrypted !== true || message._decrypt_error || !object(payload) || payload.type !== 'app.pubsub.v1'
179
+ || typeof sender !== 'string' || !sender || message.to && message.to !== this.client._aid
180
+ || !Number.isSafeInteger(payload.expires_at) || typeof payload.op !== 'string')
181
+ return true;
182
+ let admitted = true;
183
+ for (const token of tokens) {
184
+ const registered = this.pubsubReceivers.get(token);
185
+ if (!registered || registered.identity !== this.pubsubIdentity() || registered.clock() >= payload.expires_at
186
+ || (payload.service_aid ?? (payload.op === 'event' ? registered.serviceAid : undefined)) !== registered.serviceAid)
187
+ continue;
188
+ if (registered.publishReceiver) {
189
+ if (registered.serviceAid !== this.client._aid || !['authorize', 'publish', 'subscribe', 'renew', 'unsubscribe', 'catalog', 'stats', 'issue_grant', 'revoke_grant'].includes(payload.op))
190
+ continue;
191
+ }
192
+ else if (sender !== registered.serviceAid || payload.op !== 'event' && !['authorize', 'publish', 'subscribe', 'renew', 'unsubscribe', 'catalog', 'stats', 'issue_grant', 'revoke_grant'].some(op => payload.op === `${op}.result`))
193
+ continue;
194
+ if (registered.receiver(message) === false) {
195
+ admitted = false;
196
+ if (this.blockedPubsubPulls.size < 64 || this.blockedPubsubPulls.has(sender))
197
+ this.blockedPubsubPulls.set(sender, { message_class: 'sdk_control', sender_aid: sender });
198
+ }
199
+ }
200
+ return admitted;
201
+ }
202
+ resumePubsubPulls() { const pending = [...this.blockedPubsubPulls.values()]; this.blockedPubsubPulls.clear(); for (const request of pending)
203
+ this.client._delivery.queueMessageClassPull(request); }
73
204
  identity() {
74
205
  const c = this.client;
75
206
  return JSON.stringify([c._aid, c._deviceId, c._slotId, c._serviceEventIdentityVersion, c._v2RuntimeGeneration,
@@ -99,13 +230,13 @@ export class ControlMessageCoordinator {
99
230
  return { ...state, message_control_v1: streams };
100
231
  });
101
232
  }
102
- async decode(row, messageClass, group, request) {
233
+ async decode(row, messageClass, group, request, rpcOptions) {
103
234
  if (messageClassOf(row) !== messageClass)
104
235
  throw new StateError('control message class mismatch');
105
236
  let decoded;
106
237
  const envelope = row.envelope_json ?? (object(row.payload) && String(row.payload.type ?? '').startsWith('e2ee.') ? JSON.stringify(row.payload) : null);
107
238
  if (envelope) {
108
- decoded = await this.client._decryptV2Message({ ...row, envelope_json: envelope }, false, false, true, true, undefined, false, 'control_pull');
239
+ decoded = await this.client._decryptV2Message({ ...row, envelope_json: envelope }, false, false, true, true, undefined, false, 'control_pull', ...(rpcOptions ? [undefined, rpcOptions] : []));
109
240
  if (!decoded)
110
241
  throw new StateError('control message decryption is not ready');
111
242
  const headers = decoded.protected_headers;
@@ -129,22 +260,34 @@ export class ControlMessageCoordinator {
129
260
  else {
130
261
  decoded = { ...row, from: row.from_aid ?? row.from, encrypted: false };
131
262
  }
263
+ for (const key of ['sender_device_id', 'sender_slot_id'])
264
+ if (typeof row[key] === 'string') {
265
+ if (key === 'sender_device_id' && decoded[key] && decoded[key] !== row[key])
266
+ throw new StateError('control sender device mismatch');
267
+ decoded[key] = row[key];
268
+ }
132
269
  if (group) {
133
270
  decoded.group_id = request.group_id ?? request.group_aid;
134
271
  decoded.group_aid = row.group_aid ?? request.group_aid;
135
272
  }
136
273
  return { ...decoded, message_class: messageClass };
137
274
  }
138
- async consume(message) {
139
- if (message.encrypted !== true || !object(message.protected_headers))
140
- return;
275
+ async consume(message, _admission = false) {
276
+ if (message.encrypted !== true || !object(message.protected_headers) || message.protected_headers.message_class !== 'sdk_control')
277
+ return true;
141
278
  const protocol = message.protected_headers.sdk_control_protocol;
142
279
  const version = protocolVersion(message.protected_headers.sdk_control_version);
143
280
  if (typeof protocol !== 'string' || version === null || !object(message.payload))
144
- return;
281
+ return true;
282
+ if (protocol === 'aun.pubsub') {
283
+ if (version === 1)
284
+ return this.dispatchPubsub(message);
285
+ return true;
286
+ }
145
287
  if (typeof this.client._onSdkControl === 'function') {
146
288
  await this.client._onSdkControl(protocol, version, message.payload, String(message.from ?? message.from_aid ?? ''));
147
289
  }
290
+ return true;
148
291
  }
149
292
  checkScope(result, request, group, volatileGroup) {
150
293
  if (!object(result) || result.message_class !== request.message_class || !Array.isArray(result.messages))
@@ -160,35 +303,11 @@ export class ControlMessageCoordinator {
160
303
  else if (result.cursor_scope !== 'peer' || result.sender_aid !== request.sender_aid)
161
304
  throw new StateError('control Pull peer scope mismatch');
162
305
  }
163
- async renewPendingAck(saved, group, key, identity) {
164
- const c = this.client, ack = saved.pending_ack;
165
- const target = ack.up_to_seq;
166
- let after = saved.pending_ack_after_seq ?? 0;
167
- if (!Number.isSafeInteger(target) || !Number.isSafeInteger(after) || after < 0 || after > target || target > saved.after_seq)
168
- throw new StateError('invalid pending control ACK coverage');
169
- const gateKey = `message_class/${ack.message_class}/${key}|forward|class=${ack.message_class}`;
170
- let proof = '';
171
- do {
172
- this.check(identity);
173
- const request = { message_class: ack.message_class, device_id: c._deviceId, slot_id: c._slotId, after_seq: after, head_seq: target, limit: 200,
174
- ...(group ? { group_id: ack.group_id } : { cursor_scope: 'peer', sender_aid: ack.sender_aid }) };
175
- const result = await c._rpcPipeline.runPullSerialized(gateKey, () => c._rpcPipeline.rawCall(group ? 'group.v2.pull' : 'message.v2.pull', request, { pullGateKey: gateKey }), true);
176
- this.check(identity);
177
- this.checkScope(result, request, group, false);
178
- const covered = result.scanned_through_seq ?? result.covered_through_seq;
179
- if (result.head_seq !== target || !Number.isSafeInteger(covered) || covered < after || covered > target
180
- || covered === after && after < target || typeof result.scan_proof !== 'string' || !result.scan_proof)
181
- throw new StateError('control ACK renewal coverage is invalid');
182
- proof = result.scan_proof;
183
- await c._rpcPipeline.rawCall(group ? 'group.v2.ack' : 'message.v2.ack', { ...ack, up_to_seq: covered, scan_proof: proof });
184
- this.check(identity);
185
- after = covered;
186
- } while (after < target);
187
- return proof;
188
- }
189
- async call(method, params) {
306
+ async call(method, params, background = false) {
190
307
  const c = this.client;
191
308
  const request = { ...params };
309
+ delete request._rpc_background;
310
+ const rpcOptions = background ? { background: true } : undefined;
192
311
  const group = method.startsWith('group.');
193
312
  const volatileGroup = group && request.volatile === true;
194
313
  const messageClass = request.message_class ?? 'normal';
@@ -205,7 +324,7 @@ export class ControlMessageCoordinator {
205
324
  if (!group)
206
325
  request.cursor_scope = 'peer';
207
326
  if (method === 'message.peers') {
208
- const result = await c._rpcPipeline.rawCall(method, request);
327
+ const result = await c._rpcPipeline.rawCall(method, request, ...(rpcOptions ? [rpcOptions] : []));
209
328
  this.check(identity);
210
329
  if (!object(result) || result.message_class !== messageClass || !Array.isArray(result.peers))
211
330
  throw new StateError('control peer directory scope mismatch');
@@ -219,45 +338,47 @@ export class ControlMessageCoordinator {
219
338
  const lock = new Promise((resolve) => { unlock = resolve; });
220
339
  this.locks.set(key, lock);
221
340
  await preceding;
341
+ let releasePage;
222
342
  try {
223
343
  this.check(identity);
224
344
  let saved = this.load(key);
225
345
  const ackMethod = group ? 'group.v2.ack' : 'message.v2.ack';
226
346
  if (!method.endsWith('.history') && object(saved.pending_ack)) {
227
- let proof = saved.scan_proof;
347
+ delete saved.pending_ack.scan_proof;
228
348
  try {
229
- await c._rpcPipeline.rawCall(ackMethod, saved.pending_ack);
349
+ await c._rpcPipeline.rawCall(ackMethod, saved.pending_ack, ...(rpcOptions ? [rpcOptions] : []));
230
350
  }
231
351
  catch (error) {
232
352
  this.check(identity);
233
353
  if (volatileGroup && error?.data?.code === 'volatile_cursor_unavailable')
234
354
  saved = { ...saved, volatile_cursor: '' };
235
- else {
236
- if (volatileGroup || !['scan_proof_expired', 'scan_proof_unavailable'].includes(error?.data?.code))
237
- throw error;
238
- proof = await this.renewPendingAck(saved, group, key, identity);
239
- }
355
+ else
356
+ throw error;
240
357
  }
241
358
  this.check(identity);
242
- saved = { ...saved, scan_proof: proof, pending_ack: undefined, pending_ack_after_seq: undefined };
359
+ saved = { ...saved, pending_ack: undefined };
243
360
  this.save(key, saved);
244
361
  }
245
362
  if (method.endsWith('.ack') || method === 'group.ack_messages') {
246
363
  if (volatileGroup)
247
364
  request.volatile_cursor = saved.volatile_cursor ?? '';
248
365
  else {
249
- const upto = request.up_to_seq ?? request.seq ?? 0;
366
+ const upto = request.up_to_seq ?? request.seq ?? request.msg_seq ?? 0;
250
367
  if (!Number.isSafeInteger(upto) || upto < 0 || upto > Number(saved.after_seq ?? 0))
251
368
  throw new ValidationError('control ACK exceeds durable cursor');
252
369
  request.up_to_seq = upto;
253
- request.scan_proof = saved.scan_proof ?? '';
370
+ delete request.scan_proof;
254
371
  }
255
- return await c._rpcPipeline.rawCall(ackMethod, request);
372
+ return await c._rpcPipeline.rawCall(ackMethod, request, ...(rpcOptions ? [rpcOptions] : []));
256
373
  }
257
374
  const history = method.endsWith('.history');
375
+ if (!history && messageClass === 'sdk_control')
376
+ releasePage = await this.acquirePubsubPage(identity);
377
+ if (!history && !group && messageClass === 'sdk_control' && this.blockedPubsubPulls.has(String(request.sender_aid)))
378
+ return { messages: [], _pubsub_backpressure: true, has_more: true };
258
379
  delete request.window_mode;
259
380
  delete request._rpc_background;
260
- request.limit = Math.max(1, Math.min(Number(request.limit) || 50, 200));
381
+ request.limit = Math.max(1, Math.min(Number(request.limit) || 50, messageClass === 'sdk_control' ? 64 : 200));
261
382
  const after = request.after_seq ?? saved.after_seq ?? 0;
262
383
  if (!Number.isSafeInteger(after) || after < 0 || !history && after > Number(saved.after_seq ?? 0))
263
384
  throw new ValidationError('control after_seq exceeds durable cursor');
@@ -267,8 +388,8 @@ export class ControlMessageCoordinator {
267
388
  request.after_volatile_cursor ??= saved.volatile_cursor;
268
389
  }
269
390
  const wireMethod = history ? method : group ? 'group.v2.pull' : 'message.v2.pull';
270
- const gateKey = `message_class/${messageClass}/${key}|forward|class=${messageClass}`;
271
- const pull = () => c._rpcPipeline.runPullSerialized(gateKey, () => c._rpcPipeline.rawCall(wireMethod, request, { pullGateKey: gateKey }), true);
391
+ const gateKey = `message_class/${messageClass}/${group ? volatileGroup ? 'volatile_group' : 'group' : 'peer'}/${group ? request.group_id ?? request.group_aid : request.sender_aid}|forward|class=${messageClass}`;
392
+ const pull = () => c._rpcPipeline.runPullSerialized(gateKey, () => c._rpcPipeline.rawCall(wireMethod, request, { pullGateKey: gateKey, ...rpcOptions }), background);
272
393
  let result;
273
394
  try {
274
395
  result = await pull();
@@ -289,10 +410,9 @@ export class ControlMessageCoordinator {
289
410
  if (!volatileGroup && (!Number.isSafeInteger(head) || !Number.isSafeInteger(covered) || covered < 0 || covered > head
290
411
  || !history && covered < after || request.head_seq > 0 && head !== request.head_seq))
291
412
  throw new StateError('control Pull coverage is invalid');
292
- if (!history && !volatileGroup && covered > after && !(typeof result.scan_proof === 'string' && result.scan_proof))
293
- throw new StateError('control Pull scan proof is missing');
294
413
  const decoded = [], volatileCursors = [], volatileIds = [];
295
414
  let previous = history ? 0 : after;
415
+ let backpressure = false, acceptedCovered = covered;
296
416
  const rows = !group && !history
297
417
  ? [...result.messages].sort((left, right) => Number(object(right) && (right.storage === 'volatile' || Boolean(right.volatile_cursor)))
298
418
  - Number(object(left) && (left.storage === 'volatile' || Boolean(left.volatile_cursor))))
@@ -306,8 +426,17 @@ export class ControlMessageCoordinator {
306
426
  throw new StateError('control message outside verified coverage');
307
427
  previous = row.seq;
308
428
  }
309
- decoded.push(await this.decode(row, messageClass, group, request));
429
+ const deadline = Number.isSafeInteger(row.expires_at) ? row.expires_at : Number.isFinite(row.t_server) && Number.isFinite(row.ttl) ? row.t_server + row.ttl * 1000 : Date.now() + 60_000;
430
+ const message = await this.decode(row, messageClass, group, request, ...(messageClass === 'sdk_control' ? [{ background: 'event', deadline }] : []));
431
+ if (messageClass !== 'sdk_control')
432
+ decoded.push(message);
310
433
  this.check(identity);
434
+ if (!history && messageClass === 'sdk_control' && await this.consume(message, true) === false) {
435
+ backpressure = true;
436
+ if (!volatile)
437
+ acceptedCovered = Math.min(acceptedCovered, row.seq - 1);
438
+ continue;
439
+ }
311
440
  if (volatile && typeof row.volatile_cursor === 'string')
312
441
  volatileCursors.push(row.volatile_cursor);
313
442
  if (volatile && typeof row.volatile_id === 'string')
@@ -321,14 +450,12 @@ export class ControlMessageCoordinator {
321
450
  }
322
451
  }
323
452
  for (const message of decoded) {
324
- if (messageClass === 'sdk_control')
325
- await this.consume(message);
326
- else
453
+ if (messageClass !== 'sdk_control')
327
454
  await c._publishAppEvent(group ? 'group.message_created' : 'message.received', message, 'control_pull');
328
455
  this.check(identity);
329
456
  }
330
- const next = { after_seq: Math.max(Number(saved.after_seq ?? 0), covered), scan_proof: result.scan_proof ?? saved.scan_proof ?? '',
331
- volatile_cursor: result.volatile_cursor ?? result.next_volatile_cursor ?? volatileCursors.at(-1) ?? saved.volatile_cursor ?? '' };
457
+ const next = { after_seq: Math.max(Number(saved.after_seq ?? 0), acceptedCovered),
458
+ volatile_cursor: backpressure ? saved.volatile_cursor ?? '' : result.volatile_cursor ?? result.next_volatile_cursor ?? volatileCursors.at(-1) ?? saved.volatile_cursor ?? '' };
332
459
  const ack = { message_class: messageClass, device_id: c._deviceId, slot_id: c._slotId };
333
460
  if (group)
334
461
  ack.group_id = request.group_id ?? request.group_aid;
@@ -337,73 +464,31 @@ export class ControlMessageCoordinator {
337
464
  if (volatileGroup)
338
465
  Object.assign(ack, { volatile: true, volatile_cursor: next.volatile_cursor });
339
466
  else
340
- Object.assign(ack, { up_to_seq: next.after_seq, scan_proof: next.scan_proof,
467
+ Object.assign(ack, { up_to_seq: next.after_seq,
341
468
  ...(volatileCursors.length ? { volatile_cursors: volatileCursors } : {}), ...(volatileIds.length ? { volatile_ids: volatileIds } : {}) });
342
469
  const ackRequired = !volatileGroup || Boolean(next.volatile_cursor);
343
- this.save(key, { ...next, ...(ackRequired ? { pending_ack: ack, pending_ack_after_seq: after } : {}) });
470
+ this.save(key, { ...next, ...(ackRequired ? { pending_ack: ack } : {}) });
344
471
  if (ackRequired)
345
- await c._rpcPipeline.rawCall(ackMethod, ack);
472
+ await c._rpcPipeline.rawCall(ackMethod, ack, ...(rpcOptions ? [rpcOptions] : []));
346
473
  this.check(identity);
347
474
  if (ackRequired)
348
475
  this.save(key, next);
349
476
  }
350
- return { ...result, messages: messageClass === 'sdk_control' ? [] : decoded, ack_seq: covered };
477
+ return { ...result, messages: messageClass === 'sdk_control' ? [] : decoded, ack_seq: acceptedCovered, ...(backpressure ? { has_more: true, ...(this.blockedPubsubPulls.has(String(request.sender_aid)) ? { _pubsub_backpressure: true } : {}) } : {}) };
351
478
  }
352
479
  finally {
480
+ releasePage?.();
353
481
  unlock();
354
482
  if (this.locks.get(key) === lock)
355
483
  this.locks.delete(key);
356
484
  }
357
485
  }
358
- schedule(params) {
359
- const group = Boolean(params.group_id ?? params.group_aid);
360
- if (!this.supports(params.message_class, group))
361
- return;
362
- this.pending.set(this.key(params, group), { ...params });
363
- const lane = params.message_class;
364
- if (!this.workers.has(lane)) {
365
- const identity = this.identity();
366
- const worker = this.drain(identity, lane).finally(() => {
367
- if (this.workers.get(lane) !== worker)
368
- return;
369
- this.workers.delete(lane);
370
- const next = [...this.pending.values()].find((item) => item.message_class === lane);
371
- if (next && !this.client._closing)
372
- this.schedule(next);
373
- });
374
- this.workers.set(lane, worker);
375
- }
376
- }
377
- async drain(identity, lane) {
378
- while (this.pending.size && identity === this.identity()) {
379
- for (const higher of MESSAGE_CLASSES.slice(0, MESSAGE_CLASSES.indexOf(lane))) {
380
- if (this.workers.has(higher)) {
381
- await this.workers.get(higher);
382
- if (identity !== this.identity() || this.client._closing)
383
- return;
384
- }
385
- }
386
- const selected = [...this.pending.entries()].find(([, params]) => params.message_class === lane);
387
- if (!selected)
388
- break;
389
- const [key, request] = selected;
390
- this.pending.delete(key);
391
- try {
392
- const result = await this.call(request.group_id || request.group_aid ? 'group.v2.pull' : 'message.v2.pull', request);
393
- this.check(identity);
394
- if (result.has_more === true || result.volatile_has_more === true) {
395
- if (result.head_seq > 0)
396
- request.head_seq = result.head_seq;
397
- this.pending.set(key, request);
398
- }
399
- }
400
- catch (error) {
401
- this.client._clientLog.debug(`control pull deferred: ${String(error)}`);
402
- }
403
- }
404
- }
405
486
  onPush(data, group = false) {
406
487
  const messageClass = messageClassOf(data);
488
+ if (!group && object(data) && messageClass === 'sdk_control')
489
+ this.queuePubsubCandidate(data);
490
+ if (group && messageClass === 'sdk_control')
491
+ return true;
407
492
  const volatile = group && object(data) && (data.storage === 'volatile' || data.volatile === true);
408
493
  if (object(data) && data.delivery_class === 'normal' && messageClass !== 'sdk_control' && !(messageClass === 'normal' && volatile))
409
494
  return false;
@@ -419,76 +504,26 @@ export class ControlMessageCoordinator {
419
504
  }
420
505
  else
421
506
  params.sender_aid = data.sender_aid ?? data.from_aid ?? data.from ?? data.inline_message?.from_aid;
422
- if (params.group_id || params.sender_aid)
423
- this.schedule(params);
424
- return true;
425
- }
426
- async recover() {
427
- if (!this.client._aid)
428
- return;
429
- const identity = this.identity();
430
- for (const messageClass of MESSAGE_CLASSES.slice(0, 2)) {
431
- if (!this.supports(messageClass))
432
- continue;
433
- let cursor = '';
434
- do {
435
- const result = await this.call('message.peers', { message_class: messageClass, filter: 'pending', cursor });
436
- this.check(identity);
437
- for (const peer of result.peers) {
438
- const sender = peer.sender_aid ?? peer.aid;
439
- if (sender)
440
- this.schedule({ message_class: messageClass, sender_aid: sender });
441
- }
442
- if (result.has_more !== true)
443
- break;
444
- if (typeof result.next_cursor !== 'string' || !result.next_cursor || result.next_cursor === cursor)
445
- throw new StateError('control peer directory cursor did not advance');
446
- cursor = result.next_cursor;
447
- } while (cursor);
448
- }
449
- const classes = MESSAGE_CLASSES.filter((kind) => this.supports(kind, true));
450
- if (classes.length) {
451
- let cursor = '';
452
- do {
453
- const listing = await this.client._rpcPipeline.rawCall('group.list_my', { size: 200, cursor });
454
- this.check(identity);
455
- for (const entry of listing?.items ?? listing?.groups ?? []) {
456
- if (object(entry) && entry.status && entry.status !== 'active')
457
- continue;
458
- const groupId = typeof entry === 'string' ? entry : entry.group_id ?? entry.group_aid;
459
- if (!groupId)
460
- continue;
461
- await new Promise((resolve) => setTimeout(resolve, 500));
462
- this.check(identity);
463
- for (const messageClass of classes) {
464
- if (messageClass !== 'normal' && groupStreamNeedsPull(entry, messageClass, 'persistent'))
465
- this.schedule({ message_class: messageClass, group_id: groupId });
466
- if (groupStreamNeedsPull(entry, messageClass, 'volatile'))
467
- this.schedule({ message_class: messageClass, group_id: groupId, volatile: true });
468
- }
469
- }
470
- if (listing.has_more !== true)
471
- break;
472
- if (typeof listing.next_cursor !== 'string' || !listing.next_cursor || listing.next_cursor === cursor)
473
- throw new StateError('control group directory cursor did not advance');
474
- cursor = listing.next_cursor;
475
- } while (cursor);
476
- }
477
- const c = this.client;
478
- const saved = c._tokenStore.loadInstanceState?.(c._aid, c._deviceId, c._slotId)?.message_control_v1 ?? {};
479
- for (const [key, value] of Object.entries(saved)) {
480
- if (!object(value) || !value.pending_ack)
481
- continue;
482
- const [messageClass, kind, scope] = JSON.parse(key);
483
- if (this.supports(messageClass, kind !== 'peer'))
484
- this.schedule({ message_class: messageClass,
485
- ...(kind === 'peer' ? { sender_aid: scope } : { group_id: scope }), ...(kind === 'volatile_group' ? { volatile: true } : {}) });
507
+ if (!group && messageClass === 'sdk_control' && this.blockedPubsubPulls.has(String(params.sender_aid)))
508
+ return true;
509
+ if (params.group_id || params.sender_aid) {
510
+ const head = Math.max(...[data.seq, data.head_seq].filter((value) => Number.isSafeInteger(value) && value > 0), 0);
511
+ if (head > 0 && data.storage !== 'volatile' && data.volatile !== true && data.volatile_pending !== true && !data.volatile_cursor) {
512
+ const saved = this.load(this.key(params, group));
513
+ if (Number.isSafeInteger(saved.after_seq) && saved.after_seq >= head && !object(saved.pending_ack))
514
+ return true;
515
+ }
516
+ this.client._delivery.queueMessageClassPull(params);
486
517
  }
518
+ return true;
487
519
  }
488
520
  close() {
489
521
  this.epoch += 1;
490
- this.pending.clear();
491
- this.workers.clear();
522
+ for (const waiter of this.pubsubPageWaiters.splice(0))
523
+ waiter.reject(new StateError('control pull invalidated'));
524
+ this.pubsubCandidates = [];
525
+ this.pubsubBytes = 0;
526
+ this.blockedPubsubPulls.clear();
492
527
  }
493
528
  }
494
529
  //# sourceMappingURL=control-messages.js.map