@agentunion/fastaun-browser 0.5.16 → 0.5.17

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 (58) hide show
  1. package/CHANGELOG.md +309 -274
  2. package/_packed_docs/CHANGELOG.md +309 -274
  3. package/_packed_docs/INDEX.md +8 -2
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +2 -0
  5. package/_packed_docs/agent.md/SCHEMA.md +15 -15
  6. 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 +1178 -1178
  7. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1213 -1213
  8. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -43
  9. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +80 -17
  10. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +27 -6
  11. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +7 -4
  12. package/_packed_docs/sdk/09-group-rpc-manual.md +167 -35
  13. package/_packed_docs/sdk/09-message-rpc-manual.md +72 -42
  14. package/_packed_docs/sdk/09-storage-rpc-manual.md +23 -14
  15. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +16 -12
  16. package/_packed_docs/sdk/INDEX.md +12 -12
  17. package/dist/agent-md.d.ts +2 -0
  18. package/dist/agent-md.d.ts.map +1 -1
  19. package/dist/agent-md.js +7 -1
  20. package/dist/agent-md.js.map +1 -1
  21. package/dist/bundle.js +2274 -1789
  22. package/dist/client/delivery.d.ts +18 -0
  23. package/dist/client/delivery.d.ts.map +1 -1
  24. package/dist/client/delivery.js +147 -5
  25. package/dist/client/delivery.js.map +1 -1
  26. package/dist/client/group-state.d.ts +4 -0
  27. package/dist/client/group-state.d.ts.map +1 -1
  28. package/dist/client/group-state.js +63 -0
  29. package/dist/client/group-state.js.map +1 -1
  30. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  31. package/dist/client/rpc-pipeline.js +37 -4
  32. package/dist/client/rpc-pipeline.js.map +1 -1
  33. package/dist/client/v2-e2ee.d.ts +7 -1
  34. package/dist/client/v2-e2ee.d.ts.map +1 -1
  35. package/dist/client/v2-e2ee.js +180 -25
  36. package/dist/client/v2-e2ee.js.map +1 -1
  37. package/dist/client.d.ts.map +1 -1
  38. package/dist/client.js +60 -12
  39. package/dist/client.js.map +1 -1
  40. package/dist/facades.d.ts +5 -0
  41. package/dist/facades.d.ts.map +1 -1
  42. package/dist/facades.js +15 -0
  43. package/dist/facades.js.map +1 -1
  44. package/dist/group-fs.d.ts.map +1 -1
  45. package/dist/group-fs.js +8 -5
  46. package/dist/group-fs.js.map +1 -1
  47. package/dist/storage/vfs.d.ts +1 -0
  48. package/dist/storage/vfs.d.ts.map +1 -1
  49. package/dist/storage/vfs.js +68 -5
  50. package/dist/storage/vfs.js.map +1 -1
  51. package/dist/tools/cross-sdk-agent.js +34 -3
  52. package/dist/tools/cross-sdk-agent.js.map +1 -1
  53. package/dist/transport.js +34 -34
  54. package/dist/types.d.ts +16 -1
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/version.d.ts +1 -1
  57. package/dist/version.js +1 -1
  58. package/package.json +10 -10
package/dist/bundle.js CHANGED
@@ -460,7 +460,7 @@ var init_indexeddb_store = __esm({
460
460
  });
461
461
 
462
462
  // src/version.ts
463
- var VERSION = "0.5.16";
463
+ var VERSION = "0.5.17";
464
464
 
465
465
  // src/types.ts
466
466
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -5139,6 +5139,7 @@ var PUSHED_SEQS_LIMIT = 5e4;
5139
5139
  var PENDING_ORDERED_LIMIT = 5e4;
5140
5140
  var MESSAGE_RECALL_SEEN_LIMIT = 1e4;
5141
5141
  var GROUP_RECALL_SEEN_LIMIT = 1e4;
5142
+ var VOLATILE_SEEN_LIMIT = 1e4;
5142
5143
  var SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
5143
5144
  var P2P_GAP_FILL_RETRY_LIMIT = 12;
5144
5145
  var P2P_GAP_FILL_RETRY_BASE_MS = 1e3;
@@ -5203,8 +5204,7 @@ var APP_GROUP_EVENT_ENVELOPE_KEYS = [
5203
5204
  "timestamp",
5204
5205
  "t_server",
5205
5206
  "status",
5206
- "device_id",
5207
- "slot_id"
5207
+ "device_id"
5208
5208
  ];
5209
5209
  var MAX_INLINE_PENDING_ACK_NAMESPACES = 4096;
5210
5210
  function formatDeliveryError(error) {
@@ -5293,6 +5293,10 @@ var MessageDeliveryEngine = class {
5293
5293
  __publicField(this, "pendingP2PInlineAcks", null);
5294
5294
  __publicField(this, "pendingGroupInlineAcks", null);
5295
5295
  __publicField(this, "pendingGroupInlineAckNamespaces", null);
5296
+ __publicField(this, "volatileContext", "");
5297
+ __publicField(this, "volatilePullCursorValue", "");
5298
+ __publicField(this, "volatileSeenMessages", /* @__PURE__ */ new Map());
5299
+ __publicField(this, "volatileInFlightMessages", /* @__PURE__ */ new Set());
5296
5300
  // reset 后旧 inline Promise 只能自行结束,不能触碰新身份上下文的状态。
5297
5301
  __publicField(this, "inlineGeneration", 0);
5298
5302
  // 同一实例的 tracker 写入必须串行,避免旧的延迟 flush 覆盖新的 Forward 提交。
@@ -5970,6 +5974,10 @@ var MessageDeliveryEngine = class {
5970
5974
  const client = this.runtime.client;
5971
5975
  this.ensurePullOperationCurrent(ns);
5972
5976
  const response = result;
5977
+ const volatileMessages = this.volatileMessages(response.messages);
5978
+ if (method === "message.pull" && volatileMessages.length > 0) {
5979
+ await this.publishPlainVolatileMessages(volatileMessages, "pull", true);
5980
+ }
5973
5981
  const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
5974
5982
  const coordinator = this.forwardCoordinator();
5975
5983
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
@@ -6169,14 +6177,19 @@ var MessageDeliveryEngine = class {
6169
6177
  normalized = this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
6170
6178
  } else if (this.isGroupScopedEvent(event)) {
6171
6179
  payload = this.stripInlineInternalFields(payload);
6172
- normalized = this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
6180
+ normalized = this.attachAppGroupEventEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
6173
6181
  } else {
6174
6182
  normalized = payload;
6175
6183
  }
6184
+ if (this.isVolatileMessage(normalized) && isJsonObject(normalized)) {
6185
+ normalized = { ...normalized };
6186
+ delete normalized.seq;
6187
+ }
6176
6188
  const canonicalSource = canonicalDeliverySource(source);
6177
6189
  if (canonicalSource === "direct" || !isJsonObject(normalized)) return normalized;
6178
6190
  if (!this.isInstanceScopedMessageEvent(event) && !this.isGroupScopedEvent(event)) return normalized;
6179
- return { ...normalized, source: canonicalSource };
6191
+ normalized = { ...normalized, source: canonicalSource };
6192
+ return normalized;
6180
6193
  }
6181
6194
  stripInlineInternalFields(payload) {
6182
6195
  if (!isJsonObject(payload)) return payload;
@@ -6317,10 +6330,13 @@ var MessageDeliveryEngine = class {
6317
6330
  stripInternalSenderDeviceFields(payload) {
6318
6331
  if (!isJsonObject(payload)) return payload;
6319
6332
  const result = { ...payload };
6320
- delete result.sender_device_id;
6321
6333
  delete result._sender_device_id;
6322
6334
  delete result.from_device_id;
6323
6335
  delete result.from_device;
6336
+ delete result.slot_id;
6337
+ delete result.sender_slot_id;
6338
+ delete result.target_slot_id;
6339
+ delete result.owner_slot_id;
6324
6340
  return result;
6325
6341
  }
6326
6342
  recallEventFromMessage(message) {
@@ -6373,6 +6389,112 @@ var MessageDeliveryEngine = class {
6373
6389
  if (recall) return { event: "message.recalled", payload: recall };
6374
6390
  return { event: "message.received", payload: message };
6375
6391
  }
6392
+ isVolatileMessage(message) {
6393
+ return isJsonObject(message) && String(message.storage ?? "").trim() === "volatile";
6394
+ }
6395
+ volatileMessages(value) {
6396
+ if (!Array.isArray(value)) return [];
6397
+ return value.filter((item) => this.isVolatileMessage(item)).map((item) => ({ ...item }));
6398
+ }
6399
+ ensureVolatileContext() {
6400
+ const client = this.runtime.client;
6401
+ const context = JSON.stringify([client._aid ?? "", client._deviceId ?? "", client._slotId ?? ""]);
6402
+ if (context === this.volatileContext) return;
6403
+ this.volatileContext = context;
6404
+ this.volatilePullCursorValue = "";
6405
+ this.volatileSeenMessages.clear();
6406
+ this.volatileInFlightMessages.clear();
6407
+ }
6408
+ volatilePullCursor() {
6409
+ this.ensureVolatileContext();
6410
+ return this.volatilePullCursorValue;
6411
+ }
6412
+ resetVolatilePullCursor() {
6413
+ this.ensureVolatileContext();
6414
+ this.volatilePullCursorValue = "";
6415
+ }
6416
+ shouldResetVolatileCursor(cursor, error) {
6417
+ const text3 = error instanceof Error ? error.message : String(error);
6418
+ return Boolean(cursor) && cursor === this.volatilePullCursor() && text3.toLowerCase().includes("volatile cursor epoch mismatch");
6419
+ }
6420
+ volatileSeenKey(message) {
6421
+ const sender = String(message.from_aid ?? message.from ?? "").trim();
6422
+ const messageId = String(message.message_id ?? "").trim();
6423
+ const cursor = String(message.volatile_cursor ?? "").trim();
6424
+ return `${sender}|${messageId || cursor}`;
6425
+ }
6426
+ isVolatileSeen(message) {
6427
+ this.ensureVolatileContext();
6428
+ const now = Date.now();
6429
+ for (const [key, expiresAt] of this.volatileSeenMessages) {
6430
+ if (expiresAt <= now) this.volatileSeenMessages.delete(key);
6431
+ }
6432
+ return this.volatileSeenMessages.has(this.volatileSeenKey(message));
6433
+ }
6434
+ markVolatileSeen(message) {
6435
+ this.ensureVolatileContext();
6436
+ const now = Date.now();
6437
+ const expiresAt = Math.max(now + 6e4, Number(message.expires_at ?? now + 18e5) || now + 18e5);
6438
+ this.volatileSeenMessages.set(this.volatileSeenKey(message), expiresAt);
6439
+ while (this.volatileSeenMessages.size > VOLATILE_SEEN_LIMIT) {
6440
+ const oldest = this.volatileSeenMessages.keys().next().value;
6441
+ if (oldest === void 0) break;
6442
+ this.volatileSeenMessages.delete(oldest);
6443
+ }
6444
+ }
6445
+ beginVolatileDelivery(message) {
6446
+ if (this.isVolatileSeen(message)) return "seen";
6447
+ const key = this.volatileSeenKey(message);
6448
+ if (this.volatileInFlightMessages.has(key)) return "inflight";
6449
+ this.volatileInFlightMessages.add(key);
6450
+ return "claimed";
6451
+ }
6452
+ finishVolatileDelivery(message) {
6453
+ this.ensureVolatileContext();
6454
+ this.volatileInFlightMessages.delete(this.volatileSeenKey(message));
6455
+ }
6456
+ publicVolatileMessage(message) {
6457
+ const result = this.stripInternalSenderDeviceFields(message);
6458
+ delete result.seq;
6459
+ return result;
6460
+ }
6461
+ async ackVolatileMessages(messages, v2, advanceCursor) {
6462
+ const cursors = [...new Set(messages.map((item) => String(item.volatile_cursor ?? "").trim()).filter(Boolean))];
6463
+ if (cursors.length === 0) return void 0;
6464
+ const method = v2 ? "message.v2.ack" : "message.ack";
6465
+ const result = await this.runtime.client._rpcPipeline.rawCall(
6466
+ method,
6467
+ { volatile_cursors: cursors },
6468
+ { background: true }
6469
+ );
6470
+ if (advanceCursor) {
6471
+ this.ensureVolatileContext();
6472
+ this.volatilePullCursorValue = cursors[cursors.length - 1];
6473
+ }
6474
+ return result;
6475
+ }
6476
+ async publishPlainVolatileMessages(messages, source, advanceCursor) {
6477
+ const processed = [];
6478
+ for (const rawMessage of messages) {
6479
+ const message = this.publicVolatileMessage(rawMessage);
6480
+ const claim = this.beginVolatileDelivery(message);
6481
+ if (claim === "seen") {
6482
+ processed.push(rawMessage);
6483
+ continue;
6484
+ }
6485
+ if (claim === "inflight") continue;
6486
+ try {
6487
+ const appEvent = this.p2pAppEventForMessage(message);
6488
+ await this.runtime.client._publishAppEvent(appEvent.event, appEvent.payload, source);
6489
+ this.markVolatileSeen(message);
6490
+ } finally {
6491
+ this.finishVolatileDelivery(message);
6492
+ }
6493
+ processed.push(rawMessage);
6494
+ }
6495
+ if (processed.length > 0) await this.ackVolatileMessages(processed, false, advanceCursor);
6496
+ return processed.map((item) => this.publicVolatileMessage(item));
6497
+ }
6376
6498
  messageRecallDedupKey(payload) {
6377
6499
  const ids = payload.message_ids;
6378
6500
  const idPart = Array.isArray(ids) ? ids.map((i) => String(i ?? "").trim()).filter(Boolean).sort().join(",") : String(ids ?? "").trim();
@@ -6662,7 +6784,7 @@ var MessageDeliveryEngine = class {
6662
6784
  client._clientLog.debug(`_onRawMessageReceived enter: from=${data?.from ?? "-"} mid=${data?.message_id ?? "-"} seq=${data?.seq ?? "-"}`);
6663
6785
  const ns = isJsonObject(data) && client._aid && data.seq !== void 0 ? `p2p:${client._aid}` : "";
6664
6786
  const seq2 = isJsonObject(data) ? positiveSafeSequenceHint(data.seq) : 0;
6665
- if (client._v2Session && ns && seq2 > 0 && this.messageTargetsCurrentInstance(data)) {
6787
+ if (client._v2Session && ns && seq2 > 0 && !this.isVolatileMessage(data) && this.messageTargetsCurrentInstance(data)) {
6666
6788
  client._seqTracker.updateMaxSeen(ns, seq2);
6667
6789
  }
6668
6790
  const hasInlineObject = isJsonObject(data) && hasNonEmptyInlineMessage(data.inline_message);
@@ -6696,6 +6818,10 @@ var MessageDeliveryEngine = class {
6696
6818
  if (!this.messageTargetsCurrentInstance(msg)) {
6697
6819
  return;
6698
6820
  }
6821
+ if (this.isVolatileMessage(msg)) {
6822
+ await this.publishPlainVolatileMessages([msg], "push", false);
6823
+ return;
6824
+ }
6699
6825
  const legacyPushSeq = positiveSafeSequenceHint(msg.seq);
6700
6826
  const hasInlineField = hasNonEmptyInlineMessage(msg.inline_message);
6701
6827
  if (client._v2Session && client._aid && (legacyPushSeq > 0 || hasInlineField)) {
@@ -7826,6 +7952,10 @@ var MessageDeliveryEngine = class {
7826
7952
  const client = this.runtime.client;
7827
7953
  const ns = client._aid ? `p2p:${client._aid}` : "";
7828
7954
  if (!ns) return;
7955
+ if (this.isVolatileMessage(data)) {
7956
+ await client._v2E2EE.publishV2VolatileMessages([data], "push", false);
7957
+ return;
7958
+ }
7829
7959
  if (isJsonObject(data) && String(data.kind ?? "").trim() === "message.online_unread_hint") {
7830
7960
  if (client._sessionOptions?.background_sync === false) return;
7831
7961
  this.enqueueOnlineUnreadP2pHint(data);
@@ -15912,6 +16042,9 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
15912
16042
  "group.commit_state",
15913
16043
  "group.ban",
15914
16044
  "group.unban",
16045
+ "group.block",
16046
+ "group.unblock",
16047
+ "group.report_status",
15915
16048
  "group.dissolve",
15916
16049
  "group.suspend",
15917
16050
  "group.resume"
@@ -16084,12 +16217,22 @@ var RpcPipeline = class {
16084
16217
  async call(method, params2) {
16085
16218
  const client = this.runtime.client;
16086
16219
  const tStart = Date.now();
16220
+ const volatileCursor = method === "message.pull" ? String(params2?.after_volatile_cursor ?? client._delivery.volatilePullCursor() ?? "").trim() : "";
16087
16221
  client._clientLog.debug(`call enter: method=${method}`);
16088
16222
  try {
16089
16223
  const result = await this.callImpl(method, params2);
16090
16224
  client._clientLog.debug(`call exit: elapsed=${Date.now() - tStart}ms method=${method}`);
16091
16225
  return result;
16092
16226
  } catch (err) {
16227
+ if (method === "message.pull" && client._delivery.shouldResetVolatileCursor(volatileCursor, err)) {
16228
+ client._delivery.resetVolatilePullCursor();
16229
+ const retryParams = { ...params2 ?? {} };
16230
+ delete retryParams.after_volatile_cursor;
16231
+ delete retryParams.client_signature;
16232
+ const result = await this.callImpl(method, retryParams);
16233
+ client._clientLog.debug(`call exit: elapsed=${Date.now() - tStart}ms method=${method}`);
16234
+ return result;
16235
+ }
16093
16236
  client._clientLog.debug(`call exit (error): elapsed=${Date.now() - tStart}ms method=${method} err=${err instanceof Error ? err.message : String(err)}`);
16094
16237
  throw err;
16095
16238
  }
@@ -16119,6 +16262,8 @@ var RpcPipeline = class {
16119
16262
  return await client._sendV2(String(p.to ?? ""), p.payload ?? {}, {
16120
16263
  messageId: String(p.message_id ?? "") || void 0,
16121
16264
  timestamp: p.timestamp,
16265
+ ttl: p.ttl,
16266
+ deviceId: String(p.device_id ?? "").trim() || String(p.to_device_id ?? "").trim() || void 0,
16122
16267
  protectedHeaders: client._protectedHeadersFromParams(p),
16123
16268
  context: isJsonObject(p.context) ? p.context : void 0
16124
16269
  });
@@ -16193,7 +16338,8 @@ var RpcPipeline = class {
16193
16338
  if (p.window_mode === "tail") return await client._v2E2EE.pullV2TailInternal(p);
16194
16339
  const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, {
16195
16340
  gateLocked: true,
16196
- force: p.force === true
16341
+ force: p.force === true,
16342
+ afterVolatileCursor: String(p.after_volatile_cursor ?? "").trim() || void 0
16197
16343
  });
16198
16344
  return { messages };
16199
16345
  } catch (exc) {
@@ -16210,7 +16356,17 @@ var RpcPipeline = class {
16210
16356
  if (method === "message.v2.ack" || method === "message.ack" && hasMatchingV2Session(client)) {
16211
16357
  await client._ensureV2SessionReady("message.ack");
16212
16358
  client._clientLog.debug(`call route: ${method} -> V2 ack`);
16213
- return await client._ackV2(p.seq ?? p.up_to_seq);
16359
+ const volatileCursors = Array.isArray(p.volatile_cursors) ? p.volatile_cursors.map((value) => String(value ?? "").trim()).filter(Boolean) : String(p.volatile_cursor ?? "").trim() ? [String(p.volatile_cursor).trim()] : [];
16360
+ const volatileIds = Array.isArray(p.volatile_ids) ? p.volatile_ids.map((value) => String(value ?? "").trim()).filter(Boolean) : [];
16361
+ if (volatileCursors.length === 0 && volatileIds.length === 0) {
16362
+ return await client._ackV2(p.seq ?? p.up_to_seq);
16363
+ }
16364
+ return await client._ackV2(
16365
+ p.seq ?? p.up_to_seq ?? 0,
16366
+ void 0,
16367
+ volatileCursors,
16368
+ volatileIds
16369
+ );
16214
16370
  }
16215
16371
  if ((method === "group.v2.pull" || method === "group.pull") && p.group_id && (method === "group.v2.pull" || hasMatchingV2Session(client))) {
16216
16372
  await client._ensureV2SessionReady("group.pull");
@@ -16378,7 +16534,7 @@ var RpcPipeline = class {
16378
16534
  }
16379
16535
  }
16380
16536
  injectMessageCursorContext(method, params2) {
16381
- if (method !== "message.pull" && method !== "message.ack") {
16537
+ if (!["message.pull", "message.v2.pull", "message.ack", "message.v2.ack"].includes(method)) {
16382
16538
  return;
16383
16539
  }
16384
16540
  const client = this.runtime.client;
@@ -16391,6 +16547,10 @@ var RpcPipeline = class {
16391
16547
  }
16392
16548
  params2.device_id = client._deviceId;
16393
16549
  params2.slot_id = client._slotId;
16550
+ if ((method === "message.pull" || method === "message.v2.pull") && params2.after_volatile_cursor === void 0) {
16551
+ const volatileCursor = client._delivery.volatilePullCursor();
16552
+ if (volatileCursor) params2.after_volatile_cursor = volatileCursor;
16553
+ }
16394
16554
  }
16395
16555
  async applyClientSignature(method, params2) {
16396
16556
  try {
@@ -16462,7 +16622,7 @@ var RpcPipeline = class {
16462
16622
  const mode = method === "message.history" ? "history" : params2.window_mode === "tail" ? "tail" : "forward";
16463
16623
  const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? 0)}`;
16464
16624
  const limit = mode === "forward" ? Number(params2.limit ?? 50) || 50 : params2.limit;
16465
- return `p2p:${client._aid}|mode=${mode}|cursor=${cursor}|force=${String(Boolean(params2.force))}|limit=${String(limit)}`;
16625
+ return `p2p:${client._aid}|mode=${mode}|cursor=${cursor}|volatile=${String(params2.after_volatile_cursor ?? "")}|force=${String(Boolean(params2.force))}|limit=${String(limit)}`;
16466
16626
  }
16467
16627
  if (method === "group.pull" || method === "group.v2.pull" || method === "group.history") {
16468
16628
  const gid = String(params2.group_id ?? params2.group_aid ?? "").trim();
@@ -17003,6 +17163,9 @@ var RpcPipeline = class {
17003
17163
  this.postprocessGroupPull(params2, next);
17004
17164
  }
17005
17165
  }
17166
+ if ((method === "message.pull" || method === "group.pull") && isJsonObject(next) && Array.isArray(next.messages)) {
17167
+ next.messages = next.messages.map((message) => isJsonObject(message) && client._delivery ? client._delivery.stripInternalSenderDeviceFields(message) : message);
17168
+ }
17006
17169
  if (!options.skipGroupState) {
17007
17170
  next = await client._groupState.postprocessResult(method, params2, next);
17008
17171
  }
@@ -17683,721 +17846,1335 @@ var StorageLowLevel = class {
17683
17846
  }
17684
17847
  };
17685
17848
 
17686
- // src/group-fs.ts
17687
- var WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/;
17688
- var GROUP_REF_RE = /^[^:/\\][^:]*:\//;
17689
- var LOCAL_PREFIX = "local:";
17690
- function isExplicitLocalPath(value) {
17691
- return String(value || "").trim().toLowerCase().startsWith(LOCAL_PREFIX);
17849
+ // src/storage/types.ts
17850
+ function text(value, fallback = "") {
17851
+ if (value === null || value === void 0) return fallback;
17852
+ return String(value);
17692
17853
  }
17693
- function stripLocalPathPrefix(value) {
17694
- const text3 = String(value || "").trim();
17695
- return text3.toLowerCase().startsWith(LOCAL_PREFIX) ? text3.slice(LOCAL_PREFIX.length) : text3;
17854
+ function intValue(value, fallback = 0) {
17855
+ const parsed = Number(value);
17856
+ return Number.isFinite(parsed) ? Math.trunc(parsed) : fallback;
17696
17857
  }
17697
- function isGroupRemoteCopyPath(value, ...groupHints) {
17698
- if (isExplicitLocalPath(value)) return false;
17699
- return isGroupRemotePath(value) || groupHints.some(Boolean);
17858
+ function boolValue(value, fallback = false) {
17859
+ if (value === null || value === void 0) return fallback;
17860
+ return Boolean(value);
17700
17861
  }
17701
- function isGroupRemotePath(value) {
17702
- const text3 = String(value || "").trim();
17703
- if (!text3) return false;
17704
- if (isExplicitLocalPath(text3)) return false;
17705
- if (WINDOWS_DRIVE_RE.test(text3)) return false;
17706
- if (text3.startsWith("http://") || text3.startsWith("https://")) return true;
17707
- return GROUP_REF_RE.test(text3);
17862
+ function mountSourceFromRaw(raw) {
17863
+ const explicit = text(raw.mount_source);
17864
+ if (explicit) return explicit;
17865
+ const sourceAid = text(raw.source_aid);
17866
+ if (!sourceAid) return "";
17867
+ return `${sourceAid}:${keyToPath(raw.source_path)}`;
17708
17868
  }
17709
- function stripNil(params2) {
17710
- const out = {};
17711
- for (const [key, value] of Object.entries(params2 ?? {})) {
17712
- if (value !== void 0 && value !== null) out[key] = value;
17869
+ function normalizePath(path) {
17870
+ let raw = String(path || "/").replace(/\\/g, "/").trim();
17871
+ if (!raw.startsWith("/")) raw = `/${raw}`;
17872
+ raw = raw.replace(/\/+/g, "/");
17873
+ const parts = [];
17874
+ for (const part of raw.split("/")) {
17875
+ if (!part || part === ".") continue;
17876
+ if (part === "..") {
17877
+ parts.pop();
17878
+ continue;
17879
+ }
17880
+ parts.push(part);
17713
17881
  }
17714
- return out;
17882
+ return parts.length ? `/${parts.join("/")}` : "/";
17715
17883
  }
17716
- async function loadSigningAID(store, signAs) {
17717
- const loaded = store.load ? await store.load(signAs) : store.loadAsync ? await store.loadAsync(signAs) : null;
17718
- if (!loaded?.ok || !loaded.data?.aid) {
17719
- throw new Error(loaded?.error?.message || `signer identity not found: ${signAs}`);
17720
- }
17721
- if (!loaded.data.aid.privateKeyPem) {
17722
- throw new Error(`signer identity missing private key: ${signAs}`);
17723
- }
17724
- return loaded.data.aid;
17884
+ function pathToKey(path) {
17885
+ const normalized = normalizePath(path);
17886
+ return normalized === "/" ? "" : normalized.slice(1);
17725
17887
  }
17726
- function text(value) {
17727
- return value === void 0 || value === null ? "" : String(value).trim();
17888
+ function keyToPath(key) {
17889
+ return normalizePath(text(key));
17728
17890
  }
17729
- function accessTokenFromClient(client) {
17730
- const authClient = client;
17731
- if (typeof authClient.getAccessToken === "function") {
17732
- const token = text(authClient.getAccessToken());
17733
- if (token) return token;
17734
- }
17735
- const directToken = text(authClient.accessToken ?? authClient.access_token);
17736
- if (directToken) return directToken;
17737
- const identityToken = text(authClient._identity?.access_token);
17738
- if (identityToken) return identityToken;
17739
- return text(authClient._sessionParams?.access_token);
17891
+ function nameFromPath(path) {
17892
+ const cleaned = normalizePath(path).replace(/\/+$/g, "");
17893
+ if (!cleaned || cleaned === "/") return "/";
17894
+ return cleaned.split("/").pop() || "/";
17740
17895
  }
17741
- function bearerHeaders(client) {
17742
- const token = accessTokenFromClient(client);
17743
- return token ? { Authorization: `Bearer ${token}` } : void 0;
17896
+ function baseNode(raw, type, path) {
17897
+ return {
17898
+ type,
17899
+ path,
17900
+ name: text(raw.name) || nameFromPath(path),
17901
+ owner: text(raw.owner ?? raw.owner_aid),
17902
+ bucket: text(raw.bucket, "default") || "default",
17903
+ size: intValue(raw.size ?? raw.size_bytes),
17904
+ mtime: intValue(raw.mtime ?? raw.updated_at),
17905
+ contentType: text(raw.content_type),
17906
+ version: intValue(raw.version),
17907
+ mode: text(raw.mode),
17908
+ isPublic: !boolValue(raw.is_private, true),
17909
+ objectId: text(raw.object_id),
17910
+ folderId: text(raw.folder_id),
17911
+ target: text(raw.target),
17912
+ mountSource: mountSourceFromRaw(raw),
17913
+ metadata: raw.metadata && typeof raw.metadata === "object" && !Array.isArray(raw.metadata) ? raw.metadata : {}
17914
+ };
17744
17915
  }
17745
- function toExactArrayBuffer(bytes) {
17746
- return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
17916
+ function nodeFromAny(input) {
17917
+ const raw = input && typeof input === "object" ? input : {};
17918
+ const nodeType = text(raw.type ?? raw.node_type).toLowerCase();
17919
+ if (["folder", "dir", "directory"].includes(nodeType)) return baseNode(raw, "dir", keyToPath(raw.path));
17920
+ if (["symlink", "link"].includes(nodeType)) return baseNode(raw, "symlink", keyToPath(raw.path));
17921
+ if (nodeType === "mount") return baseNode(raw, "mount", keyToPath(raw.path));
17922
+ const path = keyToPath(raw.path ?? raw.object_key);
17923
+ return {
17924
+ ...baseNode(raw, "file", path),
17925
+ sha256: text(raw.sha256),
17926
+ etag: text(raw.etag)
17927
+ };
17928
+ }
17929
+ function usageFromAny(input, ownerFallback = "") {
17930
+ const raw = input && typeof input === "object" ? input : {};
17931
+ const quota = intValue(raw.quota_bytes ?? raw.quota_total_bytes);
17932
+ const used = intValue(raw.used_bytes ?? raw.quota_used_bytes);
17933
+ return {
17934
+ owner: text(raw.owner ?? raw.owner_aid, ownerFallback),
17935
+ quotaBytes: quota,
17936
+ usedBytes: used,
17937
+ availBytes: quota ? Math.max(0, quota - used) : 0,
17938
+ objectCount: intValue(raw.object_count)
17939
+ };
17747
17940
  }
17941
+ function unmountFromAny(input, fallback) {
17942
+ const raw = input && typeof input === "object" ? input : {};
17943
+ const path = keyToPath(raw.mount_path ?? raw.path ?? fallback.mountPath);
17944
+ return {
17945
+ unmounted: boolValue(raw.unmounted),
17946
+ owner: text(raw.owner ?? raw.owner_aid, fallback.owner ?? ""),
17947
+ bucket: text(raw.bucket, fallback.bucket ?? "default") || (fallback.bucket ?? "default"),
17948
+ path,
17949
+ mountPath: path
17950
+ };
17951
+ }
17952
+
17953
+ // src/storage/vfs.ts
17748
17954
  async function sha256Hex(data) {
17955
+ const copy = new Uint8Array(data.byteLength);
17956
+ copy.set(data);
17749
17957
  const subtle = globalThis.crypto?.subtle;
17750
17958
  if (!subtle?.digest) throw new StorageError("SHA-256 unavailable", "EUNSUPPORTED");
17751
- const digest = await subtle.digest("SHA-256", toExactArrayBuffer(data));
17959
+ const digest = await subtle.digest("SHA-256", copy.buffer);
17752
17960
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
17753
17961
  }
17754
- async function blobToBytes(blob) {
17755
- if (typeof blob.arrayBuffer === "function") {
17756
- return new Uint8Array(await blob.arrayBuffer());
17757
- }
17758
- if (typeof blob.stream === "function" && typeof Response !== "undefined") {
17759
- return new Uint8Array(await new Response(blob).arrayBuffer());
17760
- }
17761
- if (typeof FileReader !== "undefined") {
17762
- return await new Promise((resolve, reject) => {
17763
- const reader = new FileReader();
17764
- reader.onload = () => resolve(new Uint8Array(reader.result));
17765
- reader.onerror = () => reject(reader.error ?? new StorageError("Blob read failed", "ESTORAGE"));
17766
- reader.readAsArrayBuffer(blob);
17767
- });
17768
- }
17769
- if (typeof blob.text === "function") {
17770
- return new TextEncoder().encode(await blob.text());
17771
- }
17772
- throw new StorageError("Blob reads are unavailable in this JavaScript runtime", "EUNSUPPORTED");
17773
- }
17774
- async function toBytes(source) {
17775
- if (typeof source === "string") return new TextEncoder().encode(source);
17776
- if (source instanceof Uint8Array) return source;
17777
- if (source instanceof ArrayBuffer) return new Uint8Array(source);
17778
- if (typeof Blob !== "undefined" && source instanceof Blob) {
17779
- return blobToBytes(source);
17780
- }
17781
- if (ArrayBuffer.isView(source)) {
17782
- return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
17783
- }
17784
- throw new StorageError("unsupported group fs upload source", "EUNSUPPORTED");
17962
+ function toBytes(data) {
17963
+ if (typeof data === "string") return new TextEncoder().encode(data);
17964
+ if (data instanceof Uint8Array) return data;
17965
+ return new Uint8Array(data);
17785
17966
  }
17786
- async function importNodeModule(specifier) {
17787
- if (typeof document !== "undefined") return null;
17788
- const getBuiltinModule = globalThis.process?.getBuiltinModule;
17789
- if (typeof getBuiltinModule === "function" && specifier.startsWith("node:")) {
17967
+ function contentTypeForPath(path) {
17968
+ const ext = path.toLowerCase().split(/[?#]/, 1)[0].match(/\.[^./\\]+$/)?.[0] ?? "";
17969
+ const known = {
17970
+ ".txt": "text/plain",
17971
+ ".json": "application/json",
17972
+ ".md": "text/markdown",
17973
+ ".html": "text/html",
17974
+ ".csv": "text/csv",
17975
+ ".png": "image/png",
17976
+ ".jpg": "image/jpeg",
17977
+ ".jpeg": "image/jpeg",
17978
+ ".gif": "image/gif",
17979
+ ".webp": "image/webp",
17980
+ ".pdf": "application/pdf"
17981
+ };
17982
+ return known[ext] ?? "application/octet-stream";
17983
+ }
17984
+ function contentTypeForData(data, path = "") {
17985
+ const startsWith = (...bytes) => bytes.every((value, index) => data[index] === value);
17986
+ if (startsWith(137, 80, 78, 71, 13, 10, 26, 10)) return "image/png";
17987
+ if (startsWith(255, 216, 255)) return "image/jpeg";
17988
+ if (startsWith(71, 73, 70, 56)) return "image/gif";
17989
+ if (startsWith(66, 77)) return "image/bmp";
17990
+ if (startsWith(37, 80, 68, 70, 45)) return "application/pdf";
17991
+ if (startsWith(31, 139)) return "application/gzip";
17992
+ if (startsWith(80, 75, 3, 4) || startsWith(80, 75, 5, 6) || startsWith(80, 75, 7, 8)) return "application/zip";
17993
+ if (data.length >= 12 && startsWith(82, 73, 70, 70) && data[8] === 87 && data[9] === 69 && data[10] === 66 && data[11] === 80) return "image/webp";
17994
+ if (data.length >= 12 && data[4] === 102 && data[5] === 116 && data[6] === 121 && data[7] === 112) return "video/mp4";
17995
+ if (data.length === 0) return contentTypeForPath(path);
17996
+ if (!data.some((value) => value < 9 || value > 13 && value < 32 || value === 127)) {
17790
17997
  try {
17791
- return getBuiltinModule(specifier.slice("node:".length));
17998
+ const text3 = new TextDecoder("utf-8", { fatal: true }).decode(data);
17999
+ const trimmed = text3.trimStart();
18000
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
18001
+ try {
18002
+ JSON.parse(text3);
18003
+ return "application/json";
18004
+ } catch {
18005
+ }
18006
+ }
18007
+ if (/^<!doctype\s+html\b|^<html\b/i.test(trimmed)) return "text/html";
18008
+ if (text3.trim()) return "text/plain";
17792
18009
  } catch {
17793
18010
  }
17794
18011
  }
17795
- try {
17796
- const requireFn = Function('return typeof require !== "undefined" ? require : undefined');
17797
- const requireModule = requireFn();
17798
- if (requireModule) return requireModule(specifier);
17799
- } catch {
17800
- }
17801
- try {
17802
- const dynamicImport = Function(`return import(${JSON.stringify(specifier)})`);
17803
- return await dynamicImport();
17804
- } catch {
17805
- return null;
17806
- }
18012
+ return contentTypeForPath(path);
17807
18013
  }
17808
- async function readLocalFile(path) {
17809
- const fs = await importNodeModule("node:fs/promises");
17810
- if (!fs?.readFile) throw new StorageError("local file reads are unavailable in this JavaScript runtime", "EUNSUPPORTED", path);
17811
- return new Uint8Array(await fs.readFile(path));
18014
+ function textValue(value) {
18015
+ if (value === void 0 || value === null) return "";
18016
+ return String(value).trim();
17812
18017
  }
17813
- async function writeLocalFile(path, data, force) {
17814
- const fs = await importNodeModule("node:fs/promises");
17815
- const pathMod = await importNodeModule("node:path");
17816
- if (!fs?.writeFile || !pathMod?.dirname) return false;
17817
- await fs.mkdir(pathMod.dirname(path), { recursive: true });
17818
- try {
17819
- await fs.writeFile(path, data, force ? void 0 : { flag: "wx" });
17820
- return true;
17821
- } catch (exc) {
17822
- const code = text(exc.code);
17823
- if (code === "EEXIST") throw new StorageExistsError(`local path already exists: ${path}`, "EEXIST", path);
17824
- throw exc;
17825
- }
18018
+ function aidIssuer(aid) {
18019
+ const value = textValue(aid).replace(/^\.+|\.+$/g, "").toLowerCase();
18020
+ const separator = value.indexOf(".");
18021
+ return separator >= 0 ? value.slice(separator + 1).replace(/^\.+|\.+$/g, "") : "";
17826
18022
  }
17827
- async function localFileStatus(path) {
17828
- const fs = await importNodeModule("node:fs/promises");
17829
- if (!fs?.stat) return "unavailable";
17830
- try {
17831
- const info = await fs.stat(path);
17832
- return info.isDirectory() ? "dir" : "file";
17833
- } catch (exc) {
17834
- const code = text(exc.code);
17835
- if (code === "ENOENT") return "missing";
17836
- throw exc;
17837
- }
18023
+ function accessTokenFromClient(client) {
18024
+ const auth = client;
18025
+ return textValue(
18026
+ auth.accessToken ?? auth.access_token ?? auth._identity?.access_token ?? auth._sessionParams?.access_token ?? auth._session_params?.access_token
18027
+ );
17838
18028
  }
17839
- function guessContentType(path) {
17840
- const lower = path.toLowerCase();
17841
- if (lower.endsWith(".md")) return "text/markdown";
17842
- if (lower.endsWith(".txt")) return "text/plain";
17843
- if (lower.endsWith(".json")) return "application/json";
17844
- if (lower.endsWith(".html") || lower.endsWith(".htm")) return "text/html";
17845
- if (lower.endsWith(".png")) return "image/png";
17846
- if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
17847
- return "application/octet-stream";
18029
+ function isInlineFallbackError(error) {
18030
+ const code = String(error.code).toUpperCase();
18031
+ if (["EINLINE", "EINLINE_LIMIT", "ERR_INLINE_LIMIT", "INLINE_LIMIT", "-32033"].includes(code)) return true;
18032
+ return error.code === -32602 && error.message.toLowerCase().includes("inline");
17848
18033
  }
17849
- function downloadTarget(dst) {
17850
- if (typeof dst === "string") {
17851
- if (dst === "blob:") return { mode: "blob" };
17852
- if (dst === "bytes:") return { mode: "bytes" };
17853
- return { mode: "local", localPath: stripLocalPathPrefix(dst) };
18034
+ function parsePathLike(input, fallbackOwner) {
18035
+ if (typeof input !== "string") {
18036
+ return { owner: input.owner ?? fallbackOwner ?? null, path: input.path };
17854
18037
  }
17855
- const mode = dst.type === "blob" ? "blob" : dst.type === "bytes" ? "bytes" : dst.path || dst.localPath ? "local" : "bytes";
17856
- const localPath = dst.path ?? dst.localPath;
17857
- return { mode, localPath: localPath ? stripLocalPathPrefix(localPath) : localPath };
18038
+ const match = input.match(/^([^:]+):(\/.*)$/);
18039
+ if (match) {
18040
+ return { owner: match[1], path: match[2] };
18041
+ }
18042
+ return { owner: fallbackOwner ?? null, path: input };
17858
18043
  }
17859
- var GroupFSVFS = class {
18044
+ var StorageVFS = class {
17860
18045
  constructor(client, options = {}) {
17861
18046
  __publicField(this, "lowlevel");
18047
+ __publicField(this, "useFsRpc");
17862
18048
  __publicField(this, "client");
17863
18049
  this.client = client;
17864
18050
  this.lowlevel = options.lowlevel ?? new StorageLowLevel(client);
18051
+ this.useFsRpc = options.useFsRpc ?? true;
17865
18052
  }
17866
- async call(method, params2, path = "") {
18053
+ get defaultOwner() {
18054
+ return this.client.aid ?? null;
18055
+ }
18056
+ owner(owner) {
18057
+ return owner || this.defaultOwner;
18058
+ }
18059
+ downloadUrlAndHeaders(ticket, owner) {
18060
+ const url = textValue(ticket.logical_url ?? ticket.download_url ?? ticket.url);
18061
+ const currentAid = textValue(this.defaultOwner);
18062
+ const targetAid = textValue(ticket.owner_aid ?? owner);
18063
+ const currentIssuer = aidIssuer(currentAid);
18064
+ const targetIssuer = aidIssuer(targetAid);
18065
+ const sameIssuer = Boolean(currentAid && targetAid && (currentAid.toLowerCase() === targetAid.toLowerCase() || currentIssuer && targetIssuer && currentIssuer === targetIssuer));
18066
+ const token = sameIssuer ? accessTokenFromClient(this.client) : "";
18067
+ return { url, headers: token ? { Authorization: `Bearer ${token}` } : void 0 };
18068
+ }
18069
+ async writeBytes(path, input, options = {}) {
18070
+ const data = toBytes(input);
18071
+ const contentType = options.contentType?.trim() || contentTypeForData(data, path);
18072
+ const owner = this.owner(options.owner);
18073
+ const bucket = options.bucket ?? "default";
18074
+ const objectKey = pathToKey(path);
18075
+ const sha2566 = await sha256Hex(data);
18076
+ const overwrite = options.overwrite ?? false;
17867
18077
  try {
17868
- return await this.client.call(method, await this.prepareParams(params2));
18078
+ const check = await this.lowlevel.checkUpload({ owner, bucket, objectKey, size: data.length, sha256: sha2566 });
18079
+ if (check.within_limit === false) {
18080
+ throw new StorageError(`file size exceeds max_file_size_bytes: ${data.length}`, "E2BIG", path, check);
18081
+ }
18082
+ if (check.target_exists && !overwrite && options.expectedVersion === void 0) {
18083
+ throw new StorageExistsError(`remote path already exists: ${path}`, "EEXIST", path, check.target);
18084
+ }
18085
+ if (check.dedup_hit || check.skip_upload) {
18086
+ const completed = await this.lowlevel.completeUpload({
18087
+ owner,
18088
+ bucket,
18089
+ objectKey,
18090
+ size: data.length,
18091
+ sha256: sha2566,
18092
+ contentType,
18093
+ metadata: options.metadata,
18094
+ isPublic: options.public ?? false,
18095
+ expectedVersion: options.expectedVersion,
18096
+ skipBlob: true,
18097
+ overwrite
18098
+ });
18099
+ return nodeFromAny(completed);
18100
+ }
18101
+ if (check.inline === true) {
18102
+ return nodeFromAny(await this.lowlevel.putObject({
18103
+ owner,
18104
+ bucket,
18105
+ objectKey,
18106
+ content: data,
18107
+ contentType,
18108
+ metadata: options.metadata,
18109
+ isPublic: options.public ?? false,
18110
+ expectedVersion: options.expectedVersion,
18111
+ overwrite
18112
+ }));
18113
+ }
18114
+ const session = await this.lowlevel.createUploadSession({
18115
+ owner,
18116
+ bucket,
18117
+ objectKey,
18118
+ size: data.length,
18119
+ contentType,
18120
+ expectedVersion: options.expectedVersion,
18121
+ overwrite
18122
+ });
18123
+ const uploadUrl = String(session.upload_url ?? "");
18124
+ if (!uploadUrl) throw new StorageError(`create_upload_session did not return upload_url`, "ESTORAGE", path);
18125
+ const headers = {};
18126
+ if (session.headers && typeof session.headers === "object") {
18127
+ for (const [key, value] of Object.entries(session.headers)) {
18128
+ if (value !== void 0 && value !== null) headers[key] = String(value);
18129
+ }
18130
+ }
18131
+ for (const key of Object.keys(headers)) {
18132
+ if (key.toLowerCase() === "content-type") delete headers[key];
18133
+ }
18134
+ headers["Content-Type"] = contentType;
18135
+ await this.lowlevel.httpPut(uploadUrl, data, headers);
18136
+ return nodeFromAny(await this.lowlevel.completeUpload({
18137
+ owner,
18138
+ bucket,
18139
+ objectKey,
18140
+ sessionId: String(session.session_id ?? ""),
18141
+ size: data.length,
18142
+ sha256: sha2566,
18143
+ contentType,
18144
+ metadata: options.metadata,
18145
+ isPublic: options.public ?? false,
18146
+ expectedVersion: options.expectedVersion,
18147
+ overwrite
18148
+ }));
17869
18149
  } catch (exc) {
17870
18150
  throw mapStorageError(exc, path);
17871
18151
  }
17872
18152
  }
17873
- async prepareParams(params2) {
17874
- const next = { ...params2 ?? {} };
17875
- const signAs = text(next.sign_as ?? next.signAs);
17876
- const aidStore = next.aid_store ?? next.aidStore;
17877
- delete next.sign_as;
17878
- delete next.signAs;
17879
- delete next.aid_store;
17880
- delete next.aidStore;
17881
- if (signAs) {
17882
- const currentAid = text(this.client.aid ?? this.client._aid);
17883
- if (currentAid.toLowerCase() !== signAs.toLowerCase()) {
17884
- if (!aidStore) {
17885
- throw new Error(`group.fs operation requires aidStore to sign as ${signAs}`);
17886
- }
17887
- next._client_signature_identity = await loadSigningAID(aidStore, signAs);
17888
- }
18153
+ async readBytes(path, options = {}) {
18154
+ return (await this.readBytesWithMetadata(path, options)).data;
18155
+ }
18156
+ async readBytesWithMetadata(path, options = {}) {
18157
+ const owner = this.owner(options.owner);
18158
+ const bucket = options.bucket ?? "default";
18159
+ const objectKey = pathToKey(path);
18160
+ try {
18161
+ const result = await this.lowlevel.getObject({ owner, bucket, objectKey, token: options.token, offset: options.offset, limit: options.limit });
18162
+ return {
18163
+ data: base64ToBytes(String(result.content ?? "")),
18164
+ sha256: textValue(result.sha256)
18165
+ };
18166
+ } catch (exc) {
18167
+ const mapped = mapStorageError(exc, path);
18168
+ if (options.offset !== void 0 || options.limit !== void 0) throw mapped;
18169
+ if (!isInlineFallbackError(mapped)) throw mapped;
17889
18170
  }
17890
- return stripNil(next);
18171
+ const ticket = await this.lowlevel.createDownloadTicket({ owner, bucket, objectKey, token: options.token });
18172
+ const { url: downloadUrl, headers } = this.downloadUrlAndHeaders(ticket, owner);
18173
+ if (!downloadUrl) throw new StorageError(`create_download_ticket did not return download_url`, "ESTORAGE", path);
18174
+ return {
18175
+ data: headers ? await this.lowlevel.httpGet(downloadUrl, headers) : await this.lowlevel.httpGet(downloadUrl),
18176
+ sha256: textValue(ticket.sha256)
18177
+ };
17891
18178
  }
17892
- ls(path, options = {}) {
17893
- return this.call("group.fs.ls", { path, ...options }, path);
18179
+ async downloadFile(path, options = {}) {
18180
+ const { data, sha256: expectedSha } = await this.readBytesWithMetadata(path, options);
18181
+ const actualSha = await sha256Hex(data);
18182
+ const expected = expectedSha.toLowerCase();
18183
+ const verified = Boolean(expected) && actualSha.toLowerCase() === expected;
18184
+ if (!verified && expected && (options.verifyHash ?? true)) {
18185
+ throw new StorageError(
18186
+ `hash mismatch: expected=${expected} actual=${actualSha.toLowerCase()}`,
18187
+ "EHASH",
18188
+ path
18189
+ );
18190
+ }
18191
+ return {
18192
+ path,
18193
+ size: data.length,
18194
+ sha256: expectedSha || actualSha,
18195
+ verified,
18196
+ data
18197
+ };
17894
18198
  }
17895
- find(path, options = {}) {
17896
- return this.call("group.fs.find", { path, ...options }, path);
18199
+ async list(path, options = {}) {
18200
+ if (options.recursive) {
18201
+ const result = [];
18202
+ const pending = [path];
18203
+ while (pending.length) {
18204
+ const current = pending.shift();
18205
+ const children = await this.list(current, { ...options, recursive: false });
18206
+ result.push(...children);
18207
+ pending.push(...children.filter((node) => node.type === "dir").map((node) => node.path));
18208
+ }
18209
+ return result;
18210
+ }
18211
+ const owner = this.owner(options.owner);
18212
+ const bucket = options.bucket ?? "default";
18213
+ const key = pathToKey(path);
18214
+ const raw = await this.lowlevel.fsList({
18215
+ owner,
18216
+ bucket,
18217
+ path: key,
18218
+ page: options.page,
18219
+ size: options.size,
18220
+ marker: options.marker,
18221
+ token: options.token
18222
+ });
18223
+ const items = raw.nodes ?? raw.items ?? [];
18224
+ return items.map((item) => nodeFromAny(item));
17897
18225
  }
17898
- stat(path, options = {}) {
17899
- return this.call("group.fs.stat", { path, ...options }, path);
18226
+ async stat(path, options = {}) {
18227
+ const owner = this.owner(options.owner);
18228
+ const raw = await this.lowlevel.fsStat({ owner, bucket: options.bucket ?? "default", path: pathToKey(path), token: options.token });
18229
+ return nodeFromAny(raw);
17900
18230
  }
17901
- lstat(path, options = {}) {
17902
- return this.call("group.fs.lstat", { path, ...options }, path);
18231
+ async lstat(path, options = {}) {
18232
+ const owner = this.owner(options.owner);
18233
+ const raw = await this.lowlevel.fsLstat({ owner, bucket: options.bucket ?? "default", path: pathToKey(path), token: options.token });
18234
+ return nodeFromAny(raw);
17903
18235
  }
17904
- mkdir(path, options = {}) {
17905
- return this.call("group.fs.mkdir", { path, parents: options.parents ?? false, ...options }, path);
18236
+ async mkdir(path, options = {}) {
18237
+ const raw = await this.lowlevel.fsMkdir({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), parents: options.parents });
18238
+ return nodeFromAny(raw.node ?? raw);
17906
18239
  }
17907
- setAcl(path, options = {}) {
17908
- const grantee = options.grantee_aid ?? options.granteeAid ?? "role:admin";
17909
- const params2 = { ...options, path, grantee_aid: grantee, perms: options.perms ?? "rwx" };
17910
- delete params2.granteeAid;
17911
- return this.call("group.fs.set_acl", params2, path);
18240
+ async touch(path, options = {}) {
18241
+ const raw = await this.lowlevel.fsTouch({
18242
+ owner: this.owner(options.owner),
18243
+ bucket: options.bucket ?? "default",
18244
+ path: pathToKey(path),
18245
+ parents: options.parents,
18246
+ noCreate: options.noCreate,
18247
+ mtime: options.mtime,
18248
+ followSymlinks: options.followSymlinks
18249
+ });
18250
+ return nodeFromAny(raw.node ?? raw);
17912
18251
  }
17913
- removeAcl(path, options = {}) {
17914
- const grantee = options.grantee_aid ?? options.granteeAid ?? "role:admin";
17915
- const params2 = { ...options, path, grantee_aid: grantee };
17916
- delete params2.granteeAid;
17917
- return this.call("group.fs.remove_acl", params2, path);
18252
+ async remove(path, options = {}) {
18253
+ const raw = await this.lowlevel.fsRemove({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), recursive: options.recursive });
18254
+ return { path, removedCount: Number(raw.removed_count ?? raw.deleted_count ?? 0) };
17918
18255
  }
17919
- getAcl(path, options = {}) {
17920
- return this.call("group.fs.get_acl", { path, ...options }, path);
18256
+ async rename(src, dst, options = {}) {
18257
+ const raw = await this.lowlevel.fsRename({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", src: pathToKey(src), dst: pathToKey(dst), overwrite: options.overwrite, expectedVersion: options.expectedVersion });
18258
+ return nodeFromAny(raw.node ?? raw);
17921
18259
  }
17922
- listAcl(path, options = {}) {
17923
- return this.call("group.fs.list_acl", { path, ...options }, path);
18260
+ async copy(src, dst, options = {}) {
18261
+ const raw = await this.lowlevel.fsCopy({
18262
+ owner: this.owner(options.owner),
18263
+ bucket: options.bucket ?? "default",
18264
+ src: pathToKey(src),
18265
+ dst: pathToKey(dst),
18266
+ overwrite: options.overwrite,
18267
+ followSymlinks: options.followSymlinks,
18268
+ recursive: options.recursive,
18269
+ dstOwner: options.dstOwner,
18270
+ dstBucket: options.dstBucket
18271
+ });
18272
+ return nodeFromAny(raw.node ?? raw);
17924
18273
  }
17925
- rm(path, options = {}) {
17926
- return this.call("group.fs.rm", {
17927
- path,
17928
- recursive: options.recursive ?? false,
17929
- force: options.force ?? false,
17930
- ...options
17931
- }, path);
18274
+ async find(path, options = {}) {
18275
+ const raw = await this.lowlevel.fsFind({
18276
+ owner: this.owner(options.owner),
18277
+ bucket: options.bucket ?? "default",
18278
+ path: pathToKey(path),
18279
+ name: options.name,
18280
+ nodeType: options.nodeType,
18281
+ size: options.size,
18282
+ mtime: options.mtime,
18283
+ page: options.page,
18284
+ pageSize: options.pageSize,
18285
+ token: options.token
18286
+ });
18287
+ const items = raw.nodes ?? raw.items ?? [];
18288
+ return items.map((item) => nodeFromAny(item));
17932
18289
  }
17933
- async cp(src, dst, options = {}) {
17934
- const groupRef = options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId;
17935
- const srcGroupRef = options.src_group_aid ?? options.srcGroupAid ?? options.src_group_id ?? options.srcGroupId;
17936
- const dstGroupRef = options.dst_group_aid ?? options.dstGroupAid ?? options.dst_group_id ?? options.dstGroupId;
17937
- const srcRemote = typeof src === "string" && isGroupRemoteCopyPath(src, srcGroupRef, groupRef);
17938
- const dstRemote = typeof dst === "string" && isGroupRemoteCopyPath(dst, dstGroupRef, groupRef);
17939
- if (srcRemote && dstRemote) return this.copyRemoteToRemote(src, dst, options);
17940
- if (!srcRemote && dstRemote) return this.uploadToGroup(src, dst, options);
17941
- if (srcRemote && !dstRemote) return this.downloadFromGroup(src, dst, options);
17942
- throw new StorageError("local-to-local copy is not handled by group.fs", "EINVAL", typeof src === "string" ? src : "");
18290
+ async df(options = {}) {
18291
+ const owner = this.owner(options.owner);
18292
+ const raw = await this.lowlevel.fsDf({ owner, bucket: options.bucket ?? "default" });
18293
+ return usageFromAny(raw, owner ?? void 0);
17943
18294
  }
17944
- async mv(src, dst, options = {}) {
17945
- const groupRef = options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId;
17946
- const srcGroupRef = options.src_group_aid ?? options.srcGroupAid ?? options.src_group_id ?? options.srcGroupId;
17947
- const dstGroupRef = options.dst_group_aid ?? options.dstGroupAid ?? options.dst_group_id ?? options.dstGroupId;
17948
- if (!isGroupRemoteCopyPath(src, srcGroupRef, groupRef) || !isGroupRemoteCopyPath(dst, dstGroupRef, groupRef)) {
17949
- throw new StorageError("group.fs.mv only supports group remote paths", "EINVAL", src);
18295
+ async du(path, options = {}) {
18296
+ const nodes = await this.find(path, {
18297
+ owner: options.owner,
18298
+ bucket: options.bucket,
18299
+ page: 1,
18300
+ pageSize: options.pageSize ?? 1e3,
18301
+ token: options.token
18302
+ });
18303
+ const rootParts = pathToKey(path).split("/").filter(Boolean);
18304
+ let sizeBytes = 0;
18305
+ let fileCount = 0;
18306
+ let dirCount = 0;
18307
+ let symlinkCount = 0;
18308
+ let truncated = false;
18309
+ for (const node of nodes) {
18310
+ const nodeParts = pathToKey(node.path).split("/").filter(Boolean);
18311
+ const depth = Math.max(0, nodeParts.length - rootParts.length);
18312
+ if (options.maxDepth !== void 0 && depth > options.maxDepth) {
18313
+ truncated = true;
18314
+ continue;
18315
+ }
18316
+ if (node.type === "file") {
18317
+ fileCount += 1;
18318
+ sizeBytes += node.size;
18319
+ } else if (node.type === "dir") {
18320
+ dirCount += 1;
18321
+ } else if (node.type === "symlink") {
18322
+ symlinkCount += 1;
18323
+ }
17950
18324
  }
17951
- const params2 = this.remoteCopyParams(src, dst, options);
17952
- return this.call("group.fs.mv", params2, src);
18325
+ return {
18326
+ path,
18327
+ sizeBytes,
18328
+ fileCount,
18329
+ dirCount,
18330
+ symlinkCount,
18331
+ maxDepth: options.maxDepth,
18332
+ truncated
18333
+ };
17953
18334
  }
17954
- df(pathOrGroup, options = {}) {
17955
- const params2 = { ...options };
17956
- if (pathOrGroup !== void 0 && pathOrGroup !== null) params2.path = pathOrGroup;
17957
- return this.call("group.fs.df", params2, pathOrGroup ?? "");
18335
+ async mount(source, mountPath, options = {}) {
18336
+ const owner = this.owner(options.owner);
18337
+ const mountRef = parsePathLike(mountPath, owner);
18338
+ const sourceRef = parsePathLike(source, options.sourceOwner ?? this.defaultOwner);
18339
+ const raw = await this.lowlevel.fsMount({
18340
+ owner: mountRef.owner,
18341
+ bucket: options.bucket ?? "default",
18342
+ mountPath: pathToKey(mountRef.path),
18343
+ sourceAid: String(sourceRef.owner ?? ""),
18344
+ sourceBucket: options.sourceBucket,
18345
+ sourcePath: pathToKey(sourceRef.path),
18346
+ readonly: options.readonly,
18347
+ expiresAt: options.expiresAt ?? options.expires,
18348
+ requireApproval: options.requireApproval
18349
+ });
18350
+ return nodeFromAny(raw.node ?? raw.mount ?? raw);
17958
18351
  }
17959
- mount(path, options = {}) {
17960
- return this.call("group.fs.mount", { path, ...options }, path);
18352
+ async mountVolume(volumeId, mountPath, options = {}) {
18353
+ const owner = this.owner(options.owner);
18354
+ const mountRef = parsePathLike(mountPath, owner);
18355
+ const raw = await this.lowlevel.fsMount({
18356
+ owner: mountRef.owner,
18357
+ bucket: options.bucket ?? "default",
18358
+ mountPath: pathToKey(mountRef.path),
18359
+ readonly: options.readonly,
18360
+ expiresAt: options.expiresAt ?? options.expires,
18361
+ requireApproval: options.requireApproval,
18362
+ volumeId
18363
+ });
18364
+ return nodeFromAny(raw.node ?? raw.mount ?? raw);
17961
18365
  }
17962
- umount(path, options = {}) {
17963
- return this.call("group.fs.umount", { path, ...options }, path);
18366
+ approveMount(mountPath, options = {}) {
18367
+ const owner = this.owner(options.owner);
18368
+ const mountRef = parsePathLike(mountPath, owner);
18369
+ return this.lowlevel.fsApprove({
18370
+ owner: mountRef.owner,
18371
+ bucket: options.bucket ?? "default",
18372
+ mountPath: pathToKey(mountRef.path),
18373
+ mountId: options.mountId,
18374
+ requestId: options.requestId
18375
+ });
17964
18376
  }
17965
- remoteCopyParams(src, dst, options) {
17966
- const params2 = { ...options, src, dst };
17967
- delete params2.contentType;
17968
- delete params2.content_type;
17969
- delete params2.metadata;
17970
- delete params2.expectedVersion;
17971
- delete params2.expected_version;
17972
- delete params2.parents;
17973
- delete params2.verifyHash;
17974
- delete params2.verify_hash;
17975
- delete params2.sourceType;
17976
- delete params2.source_type;
17977
- delete params2.localPath;
17978
- delete params2.local_path;
17979
- if (options.groupAid !== void 0) params2.group_aid = options.groupAid;
17980
- if (options.groupId !== void 0) params2.group_id = options.groupId;
17981
- if (options.srcGroupAid !== void 0) params2.src_group_aid = options.srcGroupAid;
17982
- if (options.srcGroupId !== void 0) params2.src_group_id = options.srcGroupId;
17983
- if (options.dstGroupAid !== void 0) params2.dst_group_aid = options.dstGroupAid;
17984
- if (options.dstGroupId !== void 0) params2.dst_group_id = options.dstGroupId;
17985
- if (options.followSymlinks !== void 0) params2.follow_symlinks = options.followSymlinks;
17986
- delete params2.groupAid;
17987
- delete params2.srcGroupAid;
17988
- delete params2.dstGroupAid;
17989
- delete params2.followSymlinks;
17990
- if (!options.force) delete params2.force;
17991
- if (!options.recursive) delete params2.recursive;
17992
- return stripNil(params2);
18377
+ rejectMount(mountPath, options = {}) {
18378
+ const owner = this.owner(options.owner);
18379
+ const mountRef = parsePathLike(mountPath, owner);
18380
+ return this.lowlevel.fsReject({
18381
+ owner: mountRef.owner,
18382
+ bucket: options.bucket ?? "default",
18383
+ mountPath: pathToKey(mountRef.path),
18384
+ mountId: options.mountId,
18385
+ requestId: options.requestId
18386
+ });
17993
18387
  }
17994
- copyRemoteToRemote(src, dst, options) {
17995
- return this.call("group.fs.cp", this.remoteCopyParams(src, dst, options), src);
18388
+ async unmount(mountPath, options = {}) {
18389
+ const owner = this.owner(options.owner);
18390
+ const mountRef = parsePathLike(mountPath, owner);
18391
+ const raw = await this.lowlevel.fsUnmount({
18392
+ owner: mountRef.owner,
18393
+ bucket: options.bucket ?? "default",
18394
+ mountPath: pathToKey(mountRef.path)
18395
+ });
18396
+ return unmountFromAny(raw, {
18397
+ owner: mountRef.owner,
18398
+ bucket: options.bucket ?? "default",
18399
+ mountPath: pathToKey(mountRef.path)
18400
+ });
17996
18401
  }
17997
- async sourceBytes(src, options) {
17998
- if (typeof src === "string") {
17999
- const sourceType = text(options.source_type ?? options.sourceType);
18000
- if (isExplicitLocalPath(src) || sourceType === "path" || options.localPath === true || options.local_path === true) {
18001
- const localPath = stripLocalPathPrefix(src);
18002
- return { data: await readLocalFile(localPath), contentType: guessContentType(localPath) };
18003
- }
18004
- return { data: await toBytes(src), contentType: "text/plain;charset=utf-8" };
18005
- }
18006
- const data = await toBytes(src);
18007
- const blobType = typeof Blob !== "undefined" && src instanceof Blob ? text(src.type) : "";
18008
- const fileName = text(src.name);
18009
- return { data, contentType: blobType || (fileName ? guessContentType(fileName) : "application/octet-stream") };
18402
+ async symlink(target, linkPath, options = {}) {
18403
+ const raw = await this.lowlevel.createSymlink({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(linkPath), target, overwrite: options.overwrite });
18404
+ return nodeFromAny(raw.symlink ?? raw);
18010
18405
  }
18011
- async uploadToGroup(src, dst, options) {
18012
- const { data, contentType } = await this.sourceBytes(src, options);
18013
- const digest = await sha256Hex(data);
18014
- const groupAid = options.group_aid ?? options.groupAid ?? options.dst_group_aid ?? options.dstGroupAid;
18015
- const groupId = options.group_id ?? options.groupId ?? options.dst_group_id ?? options.dstGroupId;
18016
- const baseParams = stripNil({
18017
- ...options,
18018
- path: dst,
18019
- group_aid: groupAid,
18020
- group_id: groupAid ? void 0 : groupId,
18021
- size_bytes: data.byteLength,
18022
- sha256: digest,
18023
- content_type: options.content_type ?? options.contentType ?? contentType,
18024
- force: options.force ?? false,
18025
- parents: options.parents ?? true,
18026
- metadata: options.metadata,
18027
- expected_version: options.expected_version ?? options.expectedVersion
18406
+ async readlink(path, options = {}) {
18407
+ const raw = await this.lowlevel.readlink({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path) });
18408
+ return nodeFromAny(raw.symlink ?? raw);
18409
+ }
18410
+ async repoint(path, newTarget, options = {}) {
18411
+ const raw = await this.lowlevel.atomicRepoint({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), newTarget, expectedVersion: options.expectedVersion });
18412
+ if (raw.ok === false) throw new StorageConflictError("symlink version conflict", "ECONFLICT", path, raw);
18413
+ return nodeFromAny(raw.symlink ?? raw);
18414
+ }
18415
+ async renameSymlink(src, dst, options = {}) {
18416
+ const raw = await this.lowlevel.renameSymlink({
18417
+ owner: this.owner(options.owner),
18418
+ bucket: options.bucket ?? "default",
18419
+ path: pathToKey(src),
18420
+ newPath: pathToKey(dst),
18421
+ overwrite: options.overwrite,
18422
+ expectedVersion: options.expectedVersion
18028
18423
  });
18029
- delete baseParams.groupId;
18030
- delete baseParams.groupAid;
18031
- delete baseParams.dstGroupId;
18032
- delete baseParams.dstGroupAid;
18033
- delete baseParams.dst_group_aid;
18034
- delete baseParams.dst_group_id;
18035
- delete baseParams.srcGroupId;
18036
- delete baseParams.srcGroupAid;
18037
- delete baseParams.src_group_aid;
18038
- delete baseParams.src_group_id;
18039
- delete baseParams.contentType;
18040
- delete baseParams.expectedVersion;
18041
- delete baseParams.verifyHash;
18042
- delete baseParams.verify_hash;
18043
- delete baseParams.recursive;
18044
- delete baseParams.followSymlinks;
18045
- delete baseParams.follow_symlinks;
18046
- delete baseParams.sourceType;
18047
- delete baseParams.source_type;
18048
- delete baseParams.localPath;
18049
- delete baseParams.local_path;
18050
- const check = await this.call("group.fs.check_upload", baseParams, dst);
18051
- if (check && typeof check === "object") {
18052
- const raw = check;
18053
- if (raw.within_limit === false) throw new StorageError("file size exceeds group fs upload limit", "E2BIG", dst, raw);
18054
- if (raw.target_exists && !options.force && options.expected_version === void 0 && options.expectedVersion === void 0) {
18055
- throw new StorageExistsError("group fs target already exists", "EEXIST", dst, raw.target);
18056
- }
18057
- if (raw.instant || raw.dedup_hit || raw.skip_upload) {
18058
- return this.call("group.fs.complete_upload", {
18059
- ...baseParams,
18060
- skip_blob: true,
18061
- session_id: raw.session_id
18062
- }, dst);
18063
- }
18064
- }
18065
- const session = await this.call("group.fs.create_upload_session", baseParams, dst);
18066
- if (!session || typeof session !== "object") {
18067
- throw new StorageError(`group.fs.create_upload_session returned invalid response`, "ESTORAGE", dst, session);
18068
- }
18069
- const rawSession = session;
18070
- const uploadUrl = text(rawSession.upload_url ?? rawSession.url);
18071
- if (!uploadUrl) throw new StorageError("group.fs.create_upload_session did not return upload_url", "ESTORAGE", dst, session);
18072
- const headers = {};
18073
- if (rawSession.headers && typeof rawSession.headers === "object" && !Array.isArray(rawSession.headers)) {
18074
- for (const [key, value] of Object.entries(rawSession.headers)) {
18075
- if (value !== void 0 && value !== null) headers[key] = String(value);
18076
- }
18077
- }
18078
- if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) {
18079
- headers["Content-Type"] = text(baseParams.content_type) || "application/octet-stream";
18080
- }
18081
- await this.lowlevel.httpPut(uploadUrl, data, headers);
18082
- return this.call("group.fs.complete_upload", {
18083
- ...baseParams,
18084
- session_id: rawSession.session_id ?? rawSession.id
18085
- }, dst);
18424
+ if (raw.ok === false) throw new StorageConflictError("symlink version conflict", "ECONFLICT", src, raw);
18425
+ return nodeFromAny(raw.symlink ?? raw);
18086
18426
  }
18087
- async downloadFromGroup(src, dst, options) {
18088
- const target = downloadTarget(dst);
18089
- if (target.mode === "local" && target.localPath) {
18090
- const status = await localFileStatus(target.localPath);
18091
- if (status === "file" && !options.force) {
18092
- throw new StorageExistsError(`local path already exists: ${target.localPath}`, "EEXIST", target.localPath);
18093
- }
18094
- }
18095
- const groupAid = options.group_aid ?? options.groupAid ?? options.src_group_aid ?? options.srcGroupAid;
18096
- const groupId = options.group_id ?? options.groupId ?? options.src_group_id ?? options.srcGroupId;
18097
- const params2 = stripNil({
18098
- ...options,
18099
- path: src,
18100
- group_aid: groupAid,
18101
- group_id: groupAid ? void 0 : groupId
18427
+ setAcl(path, options) {
18428
+ return this.lowlevel.setAcl({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), granteeAid: options.granteeAid, perms: options.perms, expiresAt: options.expiresAt, maxUses: options.maxUses });
18429
+ }
18430
+ removeAcl(path, options) {
18431
+ return this.lowlevel.removeAcl({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), granteeAid: options.granteeAid });
18432
+ }
18433
+ listAcl(path, options = {}) {
18434
+ return this.lowlevel.listAcl({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path) });
18435
+ }
18436
+ async setVisibility(path, options) {
18437
+ const raw = await this.lowlevel.setVisibility({
18438
+ owner: this.owner(options.owner),
18439
+ bucket: options.bucket ?? "default",
18440
+ path: pathToKey(path),
18441
+ visibility: options.visibility,
18442
+ allowRoles: options.allowRoles
18102
18443
  });
18103
- delete params2.groupId;
18104
- delete params2.groupAid;
18105
- delete params2.srcGroupId;
18106
- delete params2.srcGroupAid;
18107
- delete params2.src_group_aid;
18108
- delete params2.src_group_id;
18109
- delete params2.dstGroupId;
18110
- delete params2.dstGroupAid;
18111
- delete params2.dst_group_aid;
18112
- delete params2.dst_group_id;
18113
- delete params2.force;
18114
- delete params2.recursive;
18115
- delete params2.parents;
18116
- delete params2.contentType;
18117
- delete params2.content_type;
18118
- delete params2.expectedVersion;
18119
- delete params2.expected_version;
18120
- delete params2.verifyHash;
18121
- delete params2.verify_hash;
18122
- delete params2.sourceType;
18123
- delete params2.source_type;
18124
- delete params2.localPath;
18125
- delete params2.local_path;
18126
- const ticket = await this.call("group.fs.create_download_ticket", params2, src);
18127
- if (!ticket || typeof ticket !== "object") {
18128
- throw new StorageError("group.fs.create_download_ticket returned invalid response", "ESTORAGE", src, ticket);
18129
- }
18130
- const rawTicket = ticket;
18131
- const downloadUrl = text(rawTicket.download_url ?? rawTicket.url);
18132
- if (!downloadUrl) throw new StorageError("group.fs.create_download_ticket did not return download_url", "ESTORAGE", src, ticket);
18133
- const data = await this.lowlevel.httpGet(downloadUrl, bearerHeaders(this.client));
18134
- const expectedSha = text(rawTicket.sha256);
18135
- const verifyHash = options.verify_hash ?? options.verifyHash ?? true;
18136
- const verified = !verifyHash || !expectedSha || await sha256Hex(data) === expectedSha.toLowerCase();
18137
- if (verifyHash && !verified) {
18138
- throw new StorageConflictError("download hash verification failed", "ECONFLICT", src, ticket);
18139
- }
18140
- if (target.mode === "local" && target.localPath) {
18141
- const status = await localFileStatus(target.localPath);
18142
- if (status === "dir") {
18143
- throw new StorageError("directory download target is not supported in this JavaScript runtime", "EISDIR", target.localPath);
18144
- }
18145
- if (status === "file" && !options.force) {
18146
- throw new StorageExistsError(`local path already exists: ${target.localPath}`, "EEXIST", target.localPath);
18147
- }
18148
- }
18149
- const result = {
18150
- path: src,
18151
- localPath: target.localPath,
18152
- size: data.byteLength,
18153
- sha256: expectedSha,
18154
- verified,
18155
- data
18156
- };
18157
- if (target.mode === "blob" && typeof Blob !== "undefined") {
18158
- result.blob = new Blob([toExactArrayBuffer(data)], { type: text(rawTicket.content_type) || "application/octet-stream" });
18159
- }
18160
- if (target.mode === "local" && target.localPath) {
18161
- result.wroteLocalFile = await writeLocalFile(target.localPath, data, Boolean(options.force));
18162
- }
18163
- return result;
18444
+ return nodeFromAny(raw.node ?? raw);
18445
+ }
18446
+ checkAccess(path, options = {}) {
18447
+ return this.lowlevel.checkAccess({
18448
+ owner: this.owner(options.owner),
18449
+ bucket: options.bucket ?? "default",
18450
+ path: pathToKey(path),
18451
+ operation: options.operation ?? "read",
18452
+ token: options.token,
18453
+ followSymlinks: options.followSymlinks ?? true
18454
+ });
18455
+ }
18456
+ issueToken(path, options = {}) {
18457
+ return this.lowlevel.issueToken({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), expiresAt: options.expiresAt, maxReads: options.maxReads });
18458
+ }
18459
+ revokeToken(path, options) {
18460
+ return this.lowlevel.revokeToken({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), token: options.token });
18461
+ }
18462
+ listTokens(path, options = {}) {
18463
+ return this.lowlevel.listTokens({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path) });
18464
+ }
18465
+ async getUsage(options = {}) {
18466
+ const owner = this.owner(options.owner);
18467
+ const raw = await this.lowlevel.getQuota({ owner, bucket: options.bucket ?? "default" });
18468
+ return usageFromAny(raw, owner ?? "");
18164
18469
  }
18165
18470
  };
18166
18471
 
18167
- // src/v2/crypto/canonical.ts
18168
- var encoder = new TextEncoder();
18169
- var MAX_SAFE_JSON_INTEGER = 9007199254740991;
18170
- function canonicalJson(obj) {
18171
- return encoder.encode(serialize(obj));
18472
+ // src/group-fs.ts
18473
+ var WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/;
18474
+ var GROUP_REF_RE = /^[^:/\\][^:]*:\//;
18475
+ var LOCAL_PREFIX = "local:";
18476
+ function isExplicitLocalPath(value) {
18477
+ return String(value || "").trim().toLowerCase().startsWith(LOCAL_PREFIX);
18172
18478
  }
18173
- function serialize(obj) {
18174
- if (obj === null) return "null";
18175
- if (obj === true) return "true";
18176
- if (obj === false) return "false";
18177
- if (typeof obj === "number") {
18178
- return serializeNumber(obj);
18479
+ function stripLocalPathPrefix(value) {
18480
+ const text3 = String(value || "").trim();
18481
+ return text3.toLowerCase().startsWith(LOCAL_PREFIX) ? text3.slice(LOCAL_PREFIX.length) : text3;
18482
+ }
18483
+ function isGroupRemoteCopyPath(value, ...groupHints) {
18484
+ if (isExplicitLocalPath(value)) return false;
18485
+ return isGroupRemotePath(value) || groupHints.some(Boolean);
18486
+ }
18487
+ function isGroupRemotePath(value) {
18488
+ const text3 = String(value || "").trim();
18489
+ if (!text3) return false;
18490
+ if (isExplicitLocalPath(text3)) return false;
18491
+ if (WINDOWS_DRIVE_RE.test(text3)) return false;
18492
+ if (text3.startsWith("http://") || text3.startsWith("https://")) return true;
18493
+ return GROUP_REF_RE.test(text3);
18494
+ }
18495
+ function stripNil(params2) {
18496
+ const out = {};
18497
+ for (const [key, value] of Object.entries(params2 ?? {})) {
18498
+ if (value !== void 0 && value !== null) out[key] = value;
18179
18499
  }
18180
- if (typeof obj === "string") {
18181
- return serializeString(obj);
18500
+ return out;
18501
+ }
18502
+ async function loadSigningAID(store, signAs) {
18503
+ const loaded = store.load ? await store.load(signAs) : store.loadAsync ? await store.loadAsync(signAs) : null;
18504
+ if (!loaded?.ok || !loaded.data?.aid) {
18505
+ throw new Error(loaded?.error?.message || `signer identity not found: ${signAs}`);
18182
18506
  }
18183
- if (Array.isArray(obj)) {
18184
- return serializeArray(obj);
18507
+ if (!loaded.data.aid.privateKeyPem) {
18508
+ throw new Error(`signer identity missing private key: ${signAs}`);
18185
18509
  }
18186
- if (typeof obj === "object") {
18187
- return serializeObject(obj);
18510
+ return loaded.data.aid;
18511
+ }
18512
+ function text2(value) {
18513
+ return value === void 0 || value === null ? "" : String(value).trim();
18514
+ }
18515
+ function accessTokenFromClient2(client) {
18516
+ const authClient = client;
18517
+ if (typeof authClient.getAccessToken === "function") {
18518
+ const token = text2(authClient.getAccessToken());
18519
+ if (token) return token;
18188
18520
  }
18189
- throw new TypeError(`canonicalJson: unsupported type ${typeof obj}`);
18521
+ const directToken = text2(authClient.accessToken ?? authClient.access_token);
18522
+ if (directToken) return directToken;
18523
+ const identityToken = text2(authClient._identity?.access_token);
18524
+ if (identityToken) return identityToken;
18525
+ return text2(authClient._sessionParams?.access_token);
18190
18526
  }
18191
- function serializeNumber(n) {
18192
- if (!isFinite(n)) {
18193
- throw new RangeError("canonicalJson: Infinity and NaN not allowed");
18527
+ function bearerHeaders(client) {
18528
+ const token = accessTokenFromClient2(client);
18529
+ return token ? { Authorization: `Bearer ${token}` } : void 0;
18530
+ }
18531
+ function toExactArrayBuffer(bytes) {
18532
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
18533
+ }
18534
+ async function sha256Hex2(data) {
18535
+ const subtle = globalThis.crypto?.subtle;
18536
+ if (!subtle?.digest) throw new StorageError("SHA-256 unavailable", "EUNSUPPORTED");
18537
+ const digest = await subtle.digest("SHA-256", toExactArrayBuffer(data));
18538
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
18539
+ }
18540
+ async function blobToBytes(blob) {
18541
+ if (typeof blob.arrayBuffer === "function") {
18542
+ return new Uint8Array(await blob.arrayBuffer());
18194
18543
  }
18195
- if (Object.is(n, -0)) return "0";
18196
- if (Number.isInteger(n)) {
18197
- if (Math.abs(n) > MAX_SAFE_JSON_INTEGER) {
18198
- throw new RangeError(`canonicalJson: integer outside safe range ${n}`);
18199
- }
18200
- return String(n);
18544
+ if (typeof blob.stream === "function" && typeof Response !== "undefined") {
18545
+ return new Uint8Array(await new Response(blob).arrayBuffer());
18201
18546
  }
18202
- return expandExponent(String(n));
18203
- }
18204
- function expandExponent(s) {
18205
- if (!/[eE]/.test(s)) return s;
18206
- const match = /^(-?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
18207
- if (!match) {
18208
- throw new TypeError(`canonicalJson: invalid number ${s}`);
18547
+ if (typeof FileReader !== "undefined") {
18548
+ return await new Promise((resolve, reject) => {
18549
+ const reader = new FileReader();
18550
+ reader.onload = () => resolve(new Uint8Array(reader.result));
18551
+ reader.onerror = () => reject(reader.error ?? new StorageError("Blob read failed", "ESTORAGE"));
18552
+ reader.readAsArrayBuffer(blob);
18553
+ });
18209
18554
  }
18210
- const sign = match[1] ?? "";
18211
- const intPart = match[2] ?? "";
18212
- const fracPart = match[3] ?? "";
18213
- const exp = Number(match[4]);
18214
- const digits = intPart + fracPart;
18215
- const point = intPart.length + exp;
18216
- if (point <= 0) {
18217
- return `${sign}0.${"0".repeat(-point)}${digits}`;
18555
+ if (typeof blob.text === "function") {
18556
+ return new TextEncoder().encode(await blob.text());
18218
18557
  }
18219
- if (point >= digits.length) {
18220
- return `${sign}${digits}${"0".repeat(point - digits.length)}`;
18558
+ throw new StorageError("Blob reads are unavailable in this JavaScript runtime", "EUNSUPPORTED");
18559
+ }
18560
+ async function toBytes2(source) {
18561
+ if (typeof source === "string") return new TextEncoder().encode(source);
18562
+ if (source instanceof Uint8Array) return source;
18563
+ if (source instanceof ArrayBuffer) return new Uint8Array(source);
18564
+ if (typeof Blob !== "undefined" && source instanceof Blob) {
18565
+ return blobToBytes(source);
18221
18566
  }
18222
- return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
18567
+ if (ArrayBuffer.isView(source)) {
18568
+ return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
18569
+ }
18570
+ throw new StorageError("unsupported group fs upload source", "EUNSUPPORTED");
18223
18571
  }
18224
- function serializeString(s) {
18225
- let result = '"';
18226
- for (let i = 0; i < s.length; i++) {
18227
- const ch = s[i];
18228
- const code = s.charCodeAt(i);
18229
- if (ch === '"') {
18230
- result += '\\"';
18231
- } else if (ch === "\\") {
18232
- result += "\\\\";
18233
- } else if (ch === "\b") {
18234
- result += "\\b";
18235
- } else if (ch === "\f") {
18236
- result += "\\f";
18237
- } else if (ch === "\n") {
18238
- result += "\\n";
18239
- } else if (ch === "\r") {
18240
- result += "\\r";
18241
- } else if (ch === " ") {
18242
- result += "\\t";
18243
- } else if (code < 32) {
18244
- result += "\\u" + code.toString(16).padStart(4, "0");
18245
- } else {
18246
- result += ch;
18572
+ async function importNodeModule(specifier) {
18573
+ if (typeof document !== "undefined") return null;
18574
+ const getBuiltinModule = globalThis.process?.getBuiltinModule;
18575
+ if (typeof getBuiltinModule === "function" && specifier.startsWith("node:")) {
18576
+ try {
18577
+ return getBuiltinModule(specifier.slice("node:".length));
18578
+ } catch {
18247
18579
  }
18248
18580
  }
18249
- result += '"';
18250
- return result;
18581
+ try {
18582
+ const requireFn = Function('return typeof require !== "undefined" ? require : undefined');
18583
+ const requireModule = requireFn();
18584
+ if (requireModule) return requireModule(specifier);
18585
+ } catch {
18586
+ }
18587
+ try {
18588
+ const dynamicImport = Function(`return import(${JSON.stringify(specifier)})`);
18589
+ return await dynamicImport();
18590
+ } catch {
18591
+ return null;
18592
+ }
18251
18593
  }
18252
- function serializeArray(arr) {
18253
- const items = arr.map((item) => serialize(item));
18254
- return "[" + items.join(",") + "]";
18594
+ async function readLocalFile(path) {
18595
+ const fs = await importNodeModule("node:fs/promises");
18596
+ if (!fs?.readFile) throw new StorageError("local file reads are unavailable in this JavaScript runtime", "EUNSUPPORTED", path);
18597
+ return new Uint8Array(await fs.readFile(path));
18255
18598
  }
18256
- function serializeObject(obj) {
18257
- const sortedKeys = Object.keys(obj).sort(compareCodePoints);
18258
- const pairs2 = sortedKeys.map(
18259
- (key) => serializeString(key) + ":" + serialize(obj[key])
18260
- );
18261
- return "{" + pairs2.join(",") + "}";
18599
+ async function writeLocalFile(path, data, force) {
18600
+ const fs = await importNodeModule("node:fs/promises");
18601
+ const pathMod = await importNodeModule("node:path");
18602
+ if (!fs?.writeFile || !pathMod?.dirname) return false;
18603
+ await fs.mkdir(pathMod.dirname(path), { recursive: true });
18604
+ try {
18605
+ await fs.writeFile(path, data, force ? void 0 : { flag: "wx" });
18606
+ return true;
18607
+ } catch (exc) {
18608
+ const code = text2(exc.code);
18609
+ if (code === "EEXIST") throw new StorageExistsError(`local path already exists: ${path}`, "EEXIST", path);
18610
+ throw exc;
18611
+ }
18262
18612
  }
18263
- function compareCodePoints(a, b) {
18264
- const ac = Array.from(a);
18265
- const bc = Array.from(b);
18266
- const n = Math.min(ac.length, bc.length);
18267
- for (let i = 0; i < n; i++) {
18268
- const av = ac[i].codePointAt(0) ?? 0;
18269
- const bv = bc[i].codePointAt(0) ?? 0;
18270
- if (av !== bv) return av - bv;
18613
+ async function localFileStatus(path) {
18614
+ const fs = await importNodeModule("node:fs/promises");
18615
+ if (!fs?.stat) return "unavailable";
18616
+ try {
18617
+ const info = await fs.stat(path);
18618
+ return info.isDirectory() ? "dir" : "file";
18619
+ } catch (exc) {
18620
+ const code = text2(exc.code);
18621
+ if (code === "ENOENT") return "missing";
18622
+ throw exc;
18271
18623
  }
18272
- return ac.length - bc.length;
18273
18624
  }
18274
-
18275
- // src/group-index.ts
18276
- var GROUP_INDEX_SCHEMA = "aun.group.index.v1";
18277
- var GROUP_INDEX_KEY = "group.index";
18278
- var GROUP_INDEX_SIG_ALG = "ECDSA-P256-SHA256";
18279
- var GROUP_INDEX_CACHE_VERIFICATION_VERSION = 1;
18280
- var GroupIndexMetaCache = class {
18281
- constructor() {
18282
- __publicField(this, "remote", /* @__PURE__ */ new Map());
18283
- __publicField(this, "localEtags", /* @__PURE__ */ new Map());
18284
- __publicField(this, "stale", /* @__PURE__ */ new Set());
18285
- __publicField(this, "settings", /* @__PURE__ */ new Map());
18286
- __publicField(this, "entryEtags", /* @__PURE__ */ new Map());
18287
- __publicField(this, "verificationVersions", /* @__PURE__ */ new Map());
18625
+ function downloadTarget(dst) {
18626
+ if (typeof dst === "string") {
18627
+ if (dst === "blob:") return { mode: "blob" };
18628
+ if (dst === "bytes:") return { mode: "bytes" };
18629
+ return { mode: "local", localPath: stripLocalPathPrefix(dst) };
18288
18630
  }
18289
- observeRpcMeta(meta, options) {
18290
- const groupIndexes = isRecord2(meta?.group_indexes) ? meta.group_indexes : null;
18291
- if (!groupIndexes) return;
18292
- const local = String(options.localAid ?? "");
18293
- for (const [groupAid, value] of Object.entries(groupIndexes)) {
18294
- if (!isRecord2(value)) continue;
18295
- const key = this.key(local, groupAid);
18296
- const remoteMeta = {};
18297
- for (const name of ["etag", "last_modified", "schema"]) {
18298
- if (value[name] !== void 0 && value[name] !== null) remoteMeta[name] = value[name];
18299
- }
18300
- this.remote.set(key, remoteMeta);
18301
- const remoteEtag = String(remoteMeta.etag ?? "");
18302
- if (remoteEtag && this.localEtags.get(key) !== remoteEtag) this.stale.add(key);
18303
- else if (remoteEtag) this.stale.delete(key);
18631
+ const mode = dst.type === "blob" ? "blob" : dst.type === "bytes" ? "bytes" : dst.path || dst.localPath ? "local" : "bytes";
18632
+ const localPath = dst.path ?? dst.localPath;
18633
+ return { mode, localPath: localPath ? stripLocalPathPrefix(localPath) : localPath };
18634
+ }
18635
+ var GroupFSVFS = class {
18636
+ constructor(client, options = {}) {
18637
+ __publicField(this, "lowlevel");
18638
+ __publicField(this, "client");
18639
+ this.client = client;
18640
+ this.lowlevel = options.lowlevel ?? new StorageLowLevel(client);
18641
+ }
18642
+ async call(method, params2, path = "") {
18643
+ try {
18644
+ return await this.client.call(method, await this.prepareParams(params2));
18645
+ } catch (exc) {
18646
+ throw mapStorageError(exc, path);
18304
18647
  }
18305
18648
  }
18306
- markFresh(localAid, groupAid, options) {
18307
- const key = this.key(localAid, groupAid);
18308
- const etag = String(options.etag ?? "");
18309
- const previous = this.localEtags.get(key) ?? "";
18310
- if (this.verificationVersions.get(key) === GROUP_INDEX_CACHE_VERIFICATION_VERSION && previous !== etag) {
18311
- this.verificationVersions.delete(key);
18312
- this.settings.delete(key);
18313
- this.entryEtags.delete(key);
18649
+ async prepareParams(params2) {
18650
+ const next = { ...params2 ?? {} };
18651
+ const signAs = text2(next.sign_as ?? next.signAs);
18652
+ const aidStore = next.aid_store ?? next.aidStore;
18653
+ delete next.sign_as;
18654
+ delete next.signAs;
18655
+ delete next.aid_store;
18656
+ delete next.aidStore;
18657
+ if (signAs) {
18658
+ const currentAid = text2(this.client.aid ?? this.client._aid);
18659
+ if (currentAid.toLowerCase() !== signAs.toLowerCase()) {
18660
+ if (!aidStore) {
18661
+ throw new Error(`group.fs operation requires aidStore to sign as ${signAs}`);
18662
+ }
18663
+ next._client_signature_identity = await loadSigningAID(aidStore, signAs);
18664
+ }
18314
18665
  }
18315
- this.localEtags.set(key, etag);
18316
- this.stale.delete(key);
18317
- return this.verificationVersions.get(key) ?? 0;
18666
+ return stripNil(next);
18318
18667
  }
18319
- isStale(localAid, groupAid) {
18320
- return this.stale.has(this.key(localAid, groupAid));
18668
+ ls(path, options = {}) {
18669
+ return this.call("group.fs.ls", { path, ...options }, path);
18321
18670
  }
18322
- remoteMeta(localAid, groupAid) {
18323
- const value = this.remote.get(this.key(localAid, groupAid));
18324
- return value ? { ...value } : null;
18671
+ find(path, options = {}) {
18672
+ return this.call("group.fs.find", { path, ...options }, path);
18325
18673
  }
18326
- localEtag(localAid, groupAid) {
18327
- return this.localEtags.get(this.key(localAid, groupAid)) ?? "";
18674
+ stat(path, options = {}) {
18675
+ return this.call("group.fs.stat", { path, ...options }, path);
18328
18676
  }
18329
- cachedSettings(localAid, groupAid, keys) {
18330
- if (this.verificationVersions.get(this.key(localAid, groupAid)) !== GROUP_INDEX_CACHE_VERIFICATION_VERSION) return null;
18331
- const value = this.settings.get(this.key(localAid, groupAid)) ?? {};
18332
- if (!keys.every((item) => item in value)) return null;
18333
- return Object.fromEntries(keys.filter((item) => item in value).map((item) => [item, value[item]]));
18677
+ lstat(path, options = {}) {
18678
+ return this.call("group.fs.lstat", { path, ...options }, path);
18334
18679
  }
18335
- cachedSettingsByEntries(localAid, groupAid, keys, entries) {
18336
- const key = this.key(localAid, groupAid);
18337
- if (this.verificationVersions.get(key) !== GROUP_INDEX_CACHE_VERIFICATION_VERSION) {
18338
- return { cached: {}, missing: [...keys] };
18339
- }
18340
- const value = this.settings.get(key) ?? {};
18341
- const localEntryEtags = this.entryEtags.get(key) ?? {};
18342
- const remoteEntryEtags = Object.fromEntries(entries.map((item) => [String(item.key ?? ""), String(item.etag ?? "")]));
18343
- const cached = {};
18344
- const missing = [];
18345
- for (const item of keys) {
18346
- if (item in value && localEntryEtags[item] === remoteEntryEtags[item]) {
18347
- cached[item] = value[item];
18348
- } else {
18349
- missing.push(item);
18350
- }
18351
- }
18352
- return { cached, missing };
18680
+ mkdir(path, options = {}) {
18681
+ return this.call("group.fs.mkdir", { path, parents: options.parents ?? false, ...options }, path);
18353
18682
  }
18354
- cacheSettings(localAid, groupAid, settings, options) {
18355
- const key = this.key(localAid, groupAid);
18356
- const etag = String(options?.etag ?? "");
18357
- if (!Array.isArray(options?.entries) || !etag) return;
18358
- this.settings.set(key, { ...settings });
18359
- this.entryEtags.set(key, Object.fromEntries(options.entries.map((item) => [String(item.key ?? ""), String(item.etag ?? "")]).filter(([entryKey]) => Boolean(entryKey))));
18360
- this.localEtags.set(key, etag);
18361
- this.verificationVersions.set(key, GROUP_INDEX_CACHE_VERIFICATION_VERSION);
18362
- this.stale.delete(key);
18683
+ setAcl(path, options = {}) {
18684
+ const grantee = options.grantee_aid ?? options.granteeAid ?? "role:admin";
18685
+ const params2 = { ...options, path, grantee_aid: grantee, perms: options.perms ?? "rwx" };
18686
+ delete params2.granteeAid;
18687
+ return this.call("group.fs.set_acl", params2, path);
18363
18688
  }
18364
- restore(localAid, groupAid, record) {
18365
- const key = this.key(localAid, groupAid);
18366
- if (isRecord2(record.remote_meta)) this.remote.set(key, { ...record.remote_meta });
18367
- if (Number(record.verification_version ?? 0) === GROUP_INDEX_CACHE_VERIFICATION_VERSION) {
18368
- this.verificationVersions.set(key, GROUP_INDEX_CACHE_VERIFICATION_VERSION);
18369
- this.localEtags.set(key, String(record.local_etag ?? ""));
18370
- this.settings.set(key, isRecord2(record.settings) ? { ...record.settings } : {});
18371
- this.entryEtags.set(key, isRecord2(record.entry_etags) ? Object.fromEntries(Object.entries(record.entry_etags).map(([entryKey, entryEtag]) => [entryKey, String(entryEtag ?? "")])) : {});
18372
- }
18373
- const remoteEtag = String(this.remote.get(key)?.etag ?? "");
18374
- if (remoteEtag && this.localEtags.get(key) !== remoteEtag) this.stale.add(key);
18375
- if (remoteEtag && this.localEtags.get(key) === remoteEtag) this.stale.delete(key);
18689
+ removeAcl(path, options = {}) {
18690
+ const grantee = options.grantee_aid ?? options.granteeAid ?? "role:admin";
18691
+ const params2 = { ...options, path, grantee_aid: grantee };
18692
+ delete params2.granteeAid;
18693
+ return this.call("group.fs.remove_acl", params2, path);
18376
18694
  }
18377
- key(localAid, groupAid) {
18378
- return `${String(localAid ?? "")}\0${String(groupAid ?? "")}`;
18695
+ getAcl(path, options = {}) {
18696
+ return this.call("group.fs.get_acl", { path, ...options }, path);
18379
18697
  }
18380
- };
18381
- async function computeGroupIndexBodyHash(entries) {
18382
- return `sha256:${await sha256Hex2(entriesBytes(entries))}`;
18383
- }
18384
- async function groupIndexEtag(entries) {
18385
- return `"sha256:${await sha256Hex2(entriesBytes(entries))}"`;
18386
- }
18387
- async function groupIndexSettingEtag(value) {
18388
- return `"sha256:${await sha256Hex2(encode(canonicalStringify(value)))}"`;
18389
- }
18390
- function groupIndexSigningPayload(meta, entries) {
18391
- const metaWithoutSignature = { ...meta };
18392
- delete metaWithoutSignature.signature;
18393
- const lines = [canonicalStringify(metaWithoutSignature)];
18394
- lines.push(...canonicalEntries(entries).map((item) => canonicalStringify(item)));
18395
- return encode(`${lines.join("\n")}
18396
- `);
18397
- }
18398
- async function buildSignedGroupIndex(options) {
18399
- const entries = canonicalEntries(options.entries);
18400
- const meta = {
18698
+ listAcl(path, options = {}) {
18699
+ return this.call("group.fs.list_acl", { path, ...options }, path);
18700
+ }
18701
+ rm(path, options = {}) {
18702
+ return this.call("group.fs.rm", {
18703
+ path,
18704
+ recursive: options.recursive ?? false,
18705
+ force: options.force ?? false,
18706
+ ...options
18707
+ }, path);
18708
+ }
18709
+ async cp(src, dst, options = {}) {
18710
+ const groupRef = options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId;
18711
+ const srcGroupRef = options.src_group_aid ?? options.srcGroupAid ?? options.src_group_id ?? options.srcGroupId;
18712
+ const dstGroupRef = options.dst_group_aid ?? options.dstGroupAid ?? options.dst_group_id ?? options.dstGroupId;
18713
+ const srcRemote = typeof src === "string" && isGroupRemoteCopyPath(src, srcGroupRef, groupRef);
18714
+ const dstRemote = typeof dst === "string" && isGroupRemoteCopyPath(dst, dstGroupRef, groupRef);
18715
+ if (srcRemote && dstRemote) return this.copyRemoteToRemote(src, dst, options);
18716
+ if (!srcRemote && dstRemote) return this.uploadToGroup(src, dst, options);
18717
+ if (srcRemote && !dstRemote) return this.downloadFromGroup(src, dst, options);
18718
+ throw new StorageError("local-to-local copy is not handled by group.fs", "EINVAL", typeof src === "string" ? src : "");
18719
+ }
18720
+ async mv(src, dst, options = {}) {
18721
+ const groupRef = options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId;
18722
+ const srcGroupRef = options.src_group_aid ?? options.srcGroupAid ?? options.src_group_id ?? options.srcGroupId;
18723
+ const dstGroupRef = options.dst_group_aid ?? options.dstGroupAid ?? options.dst_group_id ?? options.dstGroupId;
18724
+ if (!isGroupRemoteCopyPath(src, srcGroupRef, groupRef) || !isGroupRemoteCopyPath(dst, dstGroupRef, groupRef)) {
18725
+ throw new StorageError("group.fs.mv only supports group remote paths", "EINVAL", src);
18726
+ }
18727
+ const params2 = this.remoteCopyParams(src, dst, options);
18728
+ return this.call("group.fs.mv", params2, src);
18729
+ }
18730
+ df(pathOrGroup, options = {}) {
18731
+ const params2 = { ...options };
18732
+ if (pathOrGroup !== void 0 && pathOrGroup !== null) params2.path = pathOrGroup;
18733
+ return this.call("group.fs.df", params2, pathOrGroup ?? "");
18734
+ }
18735
+ mount(path, options = {}) {
18736
+ return this.call("group.fs.mount", { path, ...options }, path);
18737
+ }
18738
+ umount(path, options = {}) {
18739
+ return this.call("group.fs.umount", { path, ...options }, path);
18740
+ }
18741
+ remoteCopyParams(src, dst, options) {
18742
+ const params2 = { ...options, src, dst };
18743
+ delete params2.contentType;
18744
+ delete params2.content_type;
18745
+ delete params2.metadata;
18746
+ delete params2.expectedVersion;
18747
+ delete params2.expected_version;
18748
+ delete params2.parents;
18749
+ delete params2.verifyHash;
18750
+ delete params2.verify_hash;
18751
+ delete params2.sourceType;
18752
+ delete params2.source_type;
18753
+ delete params2.localPath;
18754
+ delete params2.local_path;
18755
+ if (options.groupAid !== void 0) params2.group_aid = options.groupAid;
18756
+ if (options.groupId !== void 0) params2.group_id = options.groupId;
18757
+ if (options.srcGroupAid !== void 0) params2.src_group_aid = options.srcGroupAid;
18758
+ if (options.srcGroupId !== void 0) params2.src_group_id = options.srcGroupId;
18759
+ if (options.dstGroupAid !== void 0) params2.dst_group_aid = options.dstGroupAid;
18760
+ if (options.dstGroupId !== void 0) params2.dst_group_id = options.dstGroupId;
18761
+ if (options.followSymlinks !== void 0) params2.follow_symlinks = options.followSymlinks;
18762
+ delete params2.groupAid;
18763
+ delete params2.srcGroupAid;
18764
+ delete params2.dstGroupAid;
18765
+ delete params2.followSymlinks;
18766
+ if (!options.force) delete params2.force;
18767
+ if (!options.recursive) delete params2.recursive;
18768
+ return stripNil(params2);
18769
+ }
18770
+ copyRemoteToRemote(src, dst, options) {
18771
+ return this.call("group.fs.cp", this.remoteCopyParams(src, dst, options), src);
18772
+ }
18773
+ async sourceBytes(src, options) {
18774
+ if (typeof src === "string") {
18775
+ const sourceType = text2(options.source_type ?? options.sourceType);
18776
+ if (isExplicitLocalPath(src) || sourceType === "path" || options.localPath === true || options.local_path === true) {
18777
+ const localPath = stripLocalPathPrefix(src);
18778
+ const data2 = await readLocalFile(localPath);
18779
+ return { data: data2, contentType: contentTypeForData(data2, localPath) };
18780
+ }
18781
+ return { data: await toBytes2(src), contentType: "text/plain;charset=utf-8" };
18782
+ }
18783
+ const data = await toBytes2(src);
18784
+ const fileName = text2(src.name);
18785
+ return { data, contentType: contentTypeForData(data, fileName) };
18786
+ }
18787
+ async uploadToGroup(src, dst, options) {
18788
+ const { data, contentType } = await this.sourceBytes(src, options);
18789
+ const digest = await sha256Hex2(data);
18790
+ const groupAid = options.group_aid ?? options.groupAid ?? options.dst_group_aid ?? options.dstGroupAid;
18791
+ const groupId = options.group_id ?? options.groupId ?? options.dst_group_id ?? options.dstGroupId;
18792
+ const baseParams = stripNil({
18793
+ ...options,
18794
+ path: dst,
18795
+ group_aid: groupAid,
18796
+ group_id: groupAid ? void 0 : groupId,
18797
+ size_bytes: data.byteLength,
18798
+ sha256: digest,
18799
+ content_type: options.content_type ?? options.contentType ?? contentType,
18800
+ force: options.force ?? false,
18801
+ parents: options.parents ?? true,
18802
+ metadata: options.metadata,
18803
+ expected_version: options.expected_version ?? options.expectedVersion
18804
+ });
18805
+ delete baseParams.groupId;
18806
+ delete baseParams.groupAid;
18807
+ delete baseParams.dstGroupId;
18808
+ delete baseParams.dstGroupAid;
18809
+ delete baseParams.dst_group_aid;
18810
+ delete baseParams.dst_group_id;
18811
+ delete baseParams.srcGroupId;
18812
+ delete baseParams.srcGroupAid;
18813
+ delete baseParams.src_group_aid;
18814
+ delete baseParams.src_group_id;
18815
+ delete baseParams.contentType;
18816
+ delete baseParams.expectedVersion;
18817
+ delete baseParams.verifyHash;
18818
+ delete baseParams.verify_hash;
18819
+ delete baseParams.recursive;
18820
+ delete baseParams.followSymlinks;
18821
+ delete baseParams.follow_symlinks;
18822
+ delete baseParams.sourceType;
18823
+ delete baseParams.source_type;
18824
+ delete baseParams.localPath;
18825
+ delete baseParams.local_path;
18826
+ const check = await this.call("group.fs.check_upload", baseParams, dst);
18827
+ if (check && typeof check === "object") {
18828
+ const raw = check;
18829
+ if (raw.within_limit === false) throw new StorageError("file size exceeds group fs upload limit", "E2BIG", dst, raw);
18830
+ if (raw.target_exists && !options.force && options.expected_version === void 0 && options.expectedVersion === void 0) {
18831
+ throw new StorageExistsError("group fs target already exists", "EEXIST", dst, raw.target);
18832
+ }
18833
+ if (raw.instant || raw.dedup_hit || raw.skip_upload) {
18834
+ return this.call("group.fs.complete_upload", {
18835
+ ...baseParams,
18836
+ skip_blob: true,
18837
+ session_id: raw.session_id
18838
+ }, dst);
18839
+ }
18840
+ }
18841
+ const session = await this.call("group.fs.create_upload_session", baseParams, dst);
18842
+ if (!session || typeof session !== "object") {
18843
+ throw new StorageError(`group.fs.create_upload_session returned invalid response`, "ESTORAGE", dst, session);
18844
+ }
18845
+ const rawSession = session;
18846
+ const uploadUrl = text2(rawSession.upload_url ?? rawSession.url);
18847
+ if (!uploadUrl) throw new StorageError("group.fs.create_upload_session did not return upload_url", "ESTORAGE", dst, session);
18848
+ const headers = {};
18849
+ if (rawSession.headers && typeof rawSession.headers === "object" && !Array.isArray(rawSession.headers)) {
18850
+ for (const [key, value] of Object.entries(rawSession.headers)) {
18851
+ if (value !== void 0 && value !== null) headers[key] = String(value);
18852
+ }
18853
+ }
18854
+ for (const key of Object.keys(headers)) {
18855
+ if (key.toLowerCase() === "content-type") delete headers[key];
18856
+ }
18857
+ headers["Content-Type"] = text2(baseParams.content_type) || "application/octet-stream";
18858
+ await this.lowlevel.httpPut(uploadUrl, data, headers);
18859
+ return this.call("group.fs.complete_upload", {
18860
+ ...baseParams,
18861
+ session_id: rawSession.session_id ?? rawSession.id
18862
+ }, dst);
18863
+ }
18864
+ async downloadFromGroup(src, dst, options) {
18865
+ const target = downloadTarget(dst);
18866
+ if (target.mode === "local" && target.localPath) {
18867
+ const status = await localFileStatus(target.localPath);
18868
+ if (status === "file" && !options.force) {
18869
+ throw new StorageExistsError(`local path already exists: ${target.localPath}`, "EEXIST", target.localPath);
18870
+ }
18871
+ }
18872
+ const groupAid = options.group_aid ?? options.groupAid ?? options.src_group_aid ?? options.srcGroupAid;
18873
+ const groupId = options.group_id ?? options.groupId ?? options.src_group_id ?? options.srcGroupId;
18874
+ const params2 = stripNil({
18875
+ ...options,
18876
+ path: src,
18877
+ group_aid: groupAid,
18878
+ group_id: groupAid ? void 0 : groupId
18879
+ });
18880
+ delete params2.groupId;
18881
+ delete params2.groupAid;
18882
+ delete params2.srcGroupId;
18883
+ delete params2.srcGroupAid;
18884
+ delete params2.src_group_aid;
18885
+ delete params2.src_group_id;
18886
+ delete params2.dstGroupId;
18887
+ delete params2.dstGroupAid;
18888
+ delete params2.dst_group_aid;
18889
+ delete params2.dst_group_id;
18890
+ delete params2.force;
18891
+ delete params2.recursive;
18892
+ delete params2.parents;
18893
+ delete params2.contentType;
18894
+ delete params2.content_type;
18895
+ delete params2.expectedVersion;
18896
+ delete params2.expected_version;
18897
+ delete params2.verifyHash;
18898
+ delete params2.verify_hash;
18899
+ delete params2.sourceType;
18900
+ delete params2.source_type;
18901
+ delete params2.localPath;
18902
+ delete params2.local_path;
18903
+ const ticket = await this.call("group.fs.create_download_ticket", params2, src);
18904
+ if (!ticket || typeof ticket !== "object") {
18905
+ throw new StorageError("group.fs.create_download_ticket returned invalid response", "ESTORAGE", src, ticket);
18906
+ }
18907
+ const rawTicket = ticket;
18908
+ const downloadUrl = text2(rawTicket.download_url ?? rawTicket.url);
18909
+ if (!downloadUrl) throw new StorageError("group.fs.create_download_ticket did not return download_url", "ESTORAGE", src, ticket);
18910
+ const data = await this.lowlevel.httpGet(downloadUrl, bearerHeaders(this.client));
18911
+ const expectedSha = text2(rawTicket.sha256);
18912
+ const verifyHash = options.verify_hash ?? options.verifyHash ?? true;
18913
+ const verified = !verifyHash || !expectedSha || await sha256Hex2(data) === expectedSha.toLowerCase();
18914
+ if (verifyHash && !verified) {
18915
+ throw new StorageConflictError("download hash verification failed", "ECONFLICT", src, ticket);
18916
+ }
18917
+ if (target.mode === "local" && target.localPath) {
18918
+ const status = await localFileStatus(target.localPath);
18919
+ if (status === "dir") {
18920
+ throw new StorageError("directory download target is not supported in this JavaScript runtime", "EISDIR", target.localPath);
18921
+ }
18922
+ if (status === "file" && !options.force) {
18923
+ throw new StorageExistsError(`local path already exists: ${target.localPath}`, "EEXIST", target.localPath);
18924
+ }
18925
+ }
18926
+ const result = {
18927
+ path: src,
18928
+ localPath: target.localPath,
18929
+ size: data.byteLength,
18930
+ sha256: expectedSha,
18931
+ verified,
18932
+ data
18933
+ };
18934
+ if (target.mode === "blob" && typeof Blob !== "undefined") {
18935
+ result.blob = new Blob([toExactArrayBuffer(data)], { type: text2(rawTicket.content_type) || "application/octet-stream" });
18936
+ }
18937
+ if (target.mode === "local" && target.localPath) {
18938
+ result.wroteLocalFile = await writeLocalFile(target.localPath, data, Boolean(options.force));
18939
+ }
18940
+ return result;
18941
+ }
18942
+ };
18943
+
18944
+ // src/v2/crypto/canonical.ts
18945
+ var encoder = new TextEncoder();
18946
+ var MAX_SAFE_JSON_INTEGER = 9007199254740991;
18947
+ function canonicalJson(obj) {
18948
+ return encoder.encode(serialize(obj));
18949
+ }
18950
+ function serialize(obj) {
18951
+ if (obj === null) return "null";
18952
+ if (obj === true) return "true";
18953
+ if (obj === false) return "false";
18954
+ if (typeof obj === "number") {
18955
+ return serializeNumber(obj);
18956
+ }
18957
+ if (typeof obj === "string") {
18958
+ return serializeString(obj);
18959
+ }
18960
+ if (Array.isArray(obj)) {
18961
+ return serializeArray(obj);
18962
+ }
18963
+ if (typeof obj === "object") {
18964
+ return serializeObject(obj);
18965
+ }
18966
+ throw new TypeError(`canonicalJson: unsupported type ${typeof obj}`);
18967
+ }
18968
+ function serializeNumber(n) {
18969
+ if (!isFinite(n)) {
18970
+ throw new RangeError("canonicalJson: Infinity and NaN not allowed");
18971
+ }
18972
+ if (Object.is(n, -0)) return "0";
18973
+ if (Number.isInteger(n)) {
18974
+ if (Math.abs(n) > MAX_SAFE_JSON_INTEGER) {
18975
+ throw new RangeError(`canonicalJson: integer outside safe range ${n}`);
18976
+ }
18977
+ return String(n);
18978
+ }
18979
+ return expandExponent(String(n));
18980
+ }
18981
+ function expandExponent(s) {
18982
+ if (!/[eE]/.test(s)) return s;
18983
+ const match = /^(-?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
18984
+ if (!match) {
18985
+ throw new TypeError(`canonicalJson: invalid number ${s}`);
18986
+ }
18987
+ const sign = match[1] ?? "";
18988
+ const intPart = match[2] ?? "";
18989
+ const fracPart = match[3] ?? "";
18990
+ const exp = Number(match[4]);
18991
+ const digits = intPart + fracPart;
18992
+ const point = intPart.length + exp;
18993
+ if (point <= 0) {
18994
+ return `${sign}0.${"0".repeat(-point)}${digits}`;
18995
+ }
18996
+ if (point >= digits.length) {
18997
+ return `${sign}${digits}${"0".repeat(point - digits.length)}`;
18998
+ }
18999
+ return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
19000
+ }
19001
+ function serializeString(s) {
19002
+ let result = '"';
19003
+ for (let i = 0; i < s.length; i++) {
19004
+ const ch = s[i];
19005
+ const code = s.charCodeAt(i);
19006
+ if (ch === '"') {
19007
+ result += '\\"';
19008
+ } else if (ch === "\\") {
19009
+ result += "\\\\";
19010
+ } else if (ch === "\b") {
19011
+ result += "\\b";
19012
+ } else if (ch === "\f") {
19013
+ result += "\\f";
19014
+ } else if (ch === "\n") {
19015
+ result += "\\n";
19016
+ } else if (ch === "\r") {
19017
+ result += "\\r";
19018
+ } else if (ch === " ") {
19019
+ result += "\\t";
19020
+ } else if (code < 32) {
19021
+ result += "\\u" + code.toString(16).padStart(4, "0");
19022
+ } else {
19023
+ result += ch;
19024
+ }
19025
+ }
19026
+ result += '"';
19027
+ return result;
19028
+ }
19029
+ function serializeArray(arr) {
19030
+ const items = arr.map((item) => serialize(item));
19031
+ return "[" + items.join(",") + "]";
19032
+ }
19033
+ function serializeObject(obj) {
19034
+ const sortedKeys = Object.keys(obj).sort(compareCodePoints);
19035
+ const pairs2 = sortedKeys.map(
19036
+ (key) => serializeString(key) + ":" + serialize(obj[key])
19037
+ );
19038
+ return "{" + pairs2.join(",") + "}";
19039
+ }
19040
+ function compareCodePoints(a, b) {
19041
+ const ac = Array.from(a);
19042
+ const bc = Array.from(b);
19043
+ const n = Math.min(ac.length, bc.length);
19044
+ for (let i = 0; i < n; i++) {
19045
+ const av = ac[i].codePointAt(0) ?? 0;
19046
+ const bv = bc[i].codePointAt(0) ?? 0;
19047
+ if (av !== bv) return av - bv;
19048
+ }
19049
+ return ac.length - bc.length;
19050
+ }
19051
+
19052
+ // src/group-index.ts
19053
+ var GROUP_INDEX_SCHEMA = "aun.group.index.v1";
19054
+ var GROUP_INDEX_KEY = "group.index";
19055
+ var GROUP_INDEX_SIG_ALG = "ECDSA-P256-SHA256";
19056
+ var GROUP_INDEX_CACHE_VERIFICATION_VERSION = 1;
19057
+ var GroupIndexMetaCache = class {
19058
+ constructor() {
19059
+ __publicField(this, "remote", /* @__PURE__ */ new Map());
19060
+ __publicField(this, "localEtags", /* @__PURE__ */ new Map());
19061
+ __publicField(this, "stale", /* @__PURE__ */ new Set());
19062
+ __publicField(this, "settings", /* @__PURE__ */ new Map());
19063
+ __publicField(this, "entryEtags", /* @__PURE__ */ new Map());
19064
+ __publicField(this, "verificationVersions", /* @__PURE__ */ new Map());
19065
+ }
19066
+ observeRpcMeta(meta, options) {
19067
+ const groupIndexes = isRecord2(meta?.group_indexes) ? meta.group_indexes : null;
19068
+ if (!groupIndexes) return;
19069
+ const local = String(options.localAid ?? "");
19070
+ for (const [groupAid, value] of Object.entries(groupIndexes)) {
19071
+ if (!isRecord2(value)) continue;
19072
+ const key = this.key(local, groupAid);
19073
+ const remoteMeta = {};
19074
+ for (const name of ["etag", "last_modified", "schema"]) {
19075
+ if (value[name] !== void 0 && value[name] !== null) remoteMeta[name] = value[name];
19076
+ }
19077
+ this.remote.set(key, remoteMeta);
19078
+ const remoteEtag = String(remoteMeta.etag ?? "");
19079
+ if (remoteEtag && this.localEtags.get(key) !== remoteEtag) this.stale.add(key);
19080
+ else if (remoteEtag) this.stale.delete(key);
19081
+ }
19082
+ }
19083
+ markFresh(localAid, groupAid, options) {
19084
+ const key = this.key(localAid, groupAid);
19085
+ const etag = String(options.etag ?? "");
19086
+ const previous = this.localEtags.get(key) ?? "";
19087
+ if (this.verificationVersions.get(key) === GROUP_INDEX_CACHE_VERIFICATION_VERSION && previous !== etag) {
19088
+ this.verificationVersions.delete(key);
19089
+ this.settings.delete(key);
19090
+ this.entryEtags.delete(key);
19091
+ }
19092
+ this.localEtags.set(key, etag);
19093
+ this.stale.delete(key);
19094
+ return this.verificationVersions.get(key) ?? 0;
19095
+ }
19096
+ isStale(localAid, groupAid) {
19097
+ return this.stale.has(this.key(localAid, groupAid));
19098
+ }
19099
+ remoteMeta(localAid, groupAid) {
19100
+ const value = this.remote.get(this.key(localAid, groupAid));
19101
+ return value ? { ...value } : null;
19102
+ }
19103
+ localEtag(localAid, groupAid) {
19104
+ return this.localEtags.get(this.key(localAid, groupAid)) ?? "";
19105
+ }
19106
+ cachedSettings(localAid, groupAid, keys) {
19107
+ if (this.verificationVersions.get(this.key(localAid, groupAid)) !== GROUP_INDEX_CACHE_VERIFICATION_VERSION) return null;
19108
+ const value = this.settings.get(this.key(localAid, groupAid)) ?? {};
19109
+ if (!keys.every((item) => item in value)) return null;
19110
+ return Object.fromEntries(keys.filter((item) => item in value).map((item) => [item, value[item]]));
19111
+ }
19112
+ cachedSettingsByEntries(localAid, groupAid, keys, entries) {
19113
+ const key = this.key(localAid, groupAid);
19114
+ if (this.verificationVersions.get(key) !== GROUP_INDEX_CACHE_VERIFICATION_VERSION) {
19115
+ return { cached: {}, missing: [...keys] };
19116
+ }
19117
+ const value = this.settings.get(key) ?? {};
19118
+ const localEntryEtags = this.entryEtags.get(key) ?? {};
19119
+ const remoteEntryEtags = Object.fromEntries(entries.map((item) => [String(item.key ?? ""), String(item.etag ?? "")]));
19120
+ const cached = {};
19121
+ const missing = [];
19122
+ for (const item of keys) {
19123
+ if (item in value && localEntryEtags[item] === remoteEntryEtags[item]) {
19124
+ cached[item] = value[item];
19125
+ } else {
19126
+ missing.push(item);
19127
+ }
19128
+ }
19129
+ return { cached, missing };
19130
+ }
19131
+ cacheSettings(localAid, groupAid, settings, options) {
19132
+ const key = this.key(localAid, groupAid);
19133
+ const etag = String(options?.etag ?? "");
19134
+ if (!Array.isArray(options?.entries) || !etag) return;
19135
+ this.settings.set(key, { ...settings });
19136
+ this.entryEtags.set(key, Object.fromEntries(options.entries.map((item) => [String(item.key ?? ""), String(item.etag ?? "")]).filter(([entryKey]) => Boolean(entryKey))));
19137
+ this.localEtags.set(key, etag);
19138
+ this.verificationVersions.set(key, GROUP_INDEX_CACHE_VERIFICATION_VERSION);
19139
+ this.stale.delete(key);
19140
+ }
19141
+ restore(localAid, groupAid, record) {
19142
+ const key = this.key(localAid, groupAid);
19143
+ if (isRecord2(record.remote_meta)) this.remote.set(key, { ...record.remote_meta });
19144
+ if (Number(record.verification_version ?? 0) === GROUP_INDEX_CACHE_VERIFICATION_VERSION) {
19145
+ this.verificationVersions.set(key, GROUP_INDEX_CACHE_VERIFICATION_VERSION);
19146
+ this.localEtags.set(key, String(record.local_etag ?? ""));
19147
+ this.settings.set(key, isRecord2(record.settings) ? { ...record.settings } : {});
19148
+ this.entryEtags.set(key, isRecord2(record.entry_etags) ? Object.fromEntries(Object.entries(record.entry_etags).map(([entryKey, entryEtag]) => [entryKey, String(entryEtag ?? "")])) : {});
19149
+ }
19150
+ const remoteEtag = String(this.remote.get(key)?.etag ?? "");
19151
+ if (remoteEtag && this.localEtags.get(key) !== remoteEtag) this.stale.add(key);
19152
+ if (remoteEtag && this.localEtags.get(key) === remoteEtag) this.stale.delete(key);
19153
+ }
19154
+ key(localAid, groupAid) {
19155
+ return `${String(localAid ?? "")}\0${String(groupAid ?? "")}`;
19156
+ }
19157
+ };
19158
+ async function computeGroupIndexBodyHash(entries) {
19159
+ return `sha256:${await sha256Hex3(entriesBytes(entries))}`;
19160
+ }
19161
+ async function groupIndexEtag(entries) {
19162
+ return `"sha256:${await sha256Hex3(entriesBytes(entries))}"`;
19163
+ }
19164
+ async function groupIndexSettingEtag(value) {
19165
+ return `"sha256:${await sha256Hex3(encode(canonicalStringify(value)))}"`;
19166
+ }
19167
+ function groupIndexSigningPayload(meta, entries) {
19168
+ const metaWithoutSignature = { ...meta };
19169
+ delete metaWithoutSignature.signature;
19170
+ const lines = [canonicalStringify(metaWithoutSignature)];
19171
+ lines.push(...canonicalEntries(entries).map((item) => canonicalStringify(item)));
19172
+ return encode(`${lines.join("\n")}
19173
+ `);
19174
+ }
19175
+ async function buildSignedGroupIndex(options) {
19176
+ const entries = canonicalEntries(options.entries);
19177
+ const meta = {
18401
19178
  type: "index_meta",
18402
19179
  group_aid: String(options.groupAid),
18403
19180
  etag: await groupIndexEtag(entries),
@@ -18489,7 +19266,7 @@ function canonicalStringify(value) {
18489
19266
  function encode(text3) {
18490
19267
  return new TextEncoder().encode(text3);
18491
19268
  }
18492
- async function sha256Hex2(data) {
19269
+ async function sha256Hex3(data) {
18493
19270
  const digest = await globalThis.crypto.subtle.digest("SHA-256", exactArrayBuffer(data));
18494
19271
  return bytesToHex(new Uint8Array(digest));
18495
19272
  }
@@ -18563,1222 +19340,672 @@ function indexedDocumentKeyName(params2) {
18563
19340
  var RpcFacade = class {
18564
19341
  constructor(client) {
18565
19342
  __publicField(this, "client");
18566
- this.client = client;
18567
- }
18568
- call(method, params2) {
18569
- return this.client.call(method, stripNil2(params2));
18570
- }
18571
- };
18572
- var MessageThoughtFacade = class extends RpcFacade {
18573
- put(params2) {
18574
- return this.call("message.thought.put", params2);
18575
- }
18576
- get(params2) {
18577
- return this.call("message.thought.get", params2);
18578
- }
18579
- };
18580
- var MessageFacade = class extends RpcFacade {
18581
- constructor() {
18582
- super(...arguments);
18583
- __publicField(this, "_thought");
18584
- }
18585
- get thought() {
18586
- if (!this._thought) this._thought = new MessageThoughtFacade(this.client);
18587
- return this._thought;
18588
- }
18589
- send(params2) {
18590
- return this.call("message.send", params2);
18591
- }
18592
- pull(params2) {
18593
- return this.call("message.pull", params2);
18594
- }
18595
- history(params2) {
18596
- return this.call("message.history", params2);
18597
- }
18598
- ack(params2) {
18599
- return this.call("message.ack", params2);
18600
- }
18601
- recall(params2) {
18602
- return this.call("message.recall", params2);
18603
- }
18604
- queryOnline(params2) {
18605
- return this.call("message.query_online", params2);
18606
- }
18607
- };
18608
- function normalizeInviteParams(params2) {
18609
- const out = stripNil2(params2);
18610
- const rawGroupId = out.group_id ?? out.groupId ?? out.group_aid ?? out.groupAid;
18611
- if (rawGroupId !== void 0) {
18612
- out.group_id = normalizeGroupId(rawGroupId);
18613
- delete out.groupId;
18614
- delete out.group_aid;
18615
- delete out.groupAid;
18616
- }
18617
- for (const [alias, canonical] of [["inviteId", "invite_id"], ["inviteeAid", "invitee_aid"]]) {
18618
- if (out[canonical] === void 0 && out[alias] !== void 0) out[canonical] = out[alias];
18619
- delete out[alias];
18620
- }
18621
- return out;
18622
- }
18623
- var MailFacade = class extends RpcFacade {
18624
- send(params2) {
18625
- return this.call("mail.send", params2);
18626
- }
18627
- get(params2) {
18628
- return this.call("mail.get", params2);
18629
- }
18630
- list(params2) {
18631
- return this.call("mail.list", params2);
18632
- }
18633
- delete(params2) {
18634
- return this.call("mail.delete", params2);
18635
- }
18636
- move(params2) {
18637
- return this.call("mail.move", params2);
18638
- }
18639
- mark(params2) {
18640
- return this.call("mail.mark", params2);
18641
- }
18642
- search(params2) {
18643
- return this.call("mail.search", params2);
18644
- }
18645
- getAttachment(params2) {
18646
- return this.call("mail.get_attachment", params2);
18647
- }
18648
- getQuota(params2) {
18649
- return this.call("mail.get_quota", params2);
18650
- }
18651
- health(params2) {
18652
- return this.call("mail.health", params2);
18653
- }
18654
- status(params2) {
18655
- return this.call("mail.status", params2);
18656
- }
18657
- };
18658
- var GroupThoughtFacade = class extends RpcFacade {
18659
- put(params2) {
18660
- return this.call("group.thought.put", params2);
18661
- }
18662
- get(params2) {
18663
- return this.call("group.thought.get", params2);
18664
- }
18665
- };
18666
- var GroupFacade = class extends RpcFacade {
18667
- constructor() {
18668
- super(...arguments);
18669
- __publicField(this, "_fs");
18670
- __publicField(this, "_thought");
18671
- }
18672
- get fs() {
18673
- if (!this._fs) this._fs = new GroupFSVFS(this.client);
18674
- return this._fs;
18675
- }
18676
- get thought() {
18677
- if (!this._thought) this._thought = new GroupThoughtFacade(this.client);
18678
- return this._thought;
18679
- }
18680
- create(params2) {
18681
- const split = splitAidStore(params2);
18682
- if (split.params.group_name === void 0 && split.params.groupName !== void 0) {
18683
- split.params.group_name = split.params.groupName;
18684
- delete split.params.groupName;
18685
- }
18686
- if (split.aidStore && typeof this.client.createGroup === "function") {
18687
- return this.client.createGroup(split.params, { aidStore: split.aidStore });
18688
- }
18689
- return this.call("group.create", split.params);
18690
- }
18691
- bindAid(params2) {
18692
- return this.call("group.bind_aid", splitAidStore(params2).params);
18693
- }
18694
- bindGroupAid(params2) {
18695
- const split = splitAidStore(params2);
18696
- if (split.aidStore && typeof this.client.bindGroupAid === "function") {
18697
- return this.client.bindGroupAid(split.params, { aidStore: split.aidStore });
18698
- }
18699
- return this.call("group.bind_group_aid", split.params);
18700
- }
18701
- renewGroupAid(params2) {
18702
- const split = splitAidStore(params2);
18703
- if (typeof this.client.renewGroupAid === "function") {
18704
- return this.client.renewGroupAid(split.params, { aidStore: split.aidStore });
18705
- }
18706
- return this.call("group.renew_group_aid", split.params);
18707
- }
18708
- getInfo(params2) {
18709
- return this.call("group.get_info", params2);
18710
- }
18711
- update(params2) {
18712
- return this.call("group.update", params2);
18713
- }
18714
- list(params2) {
18715
- return this.call("group.list", params2);
18716
- }
18717
- listMy(params2) {
18718
- return this.call("group.list_my", params2);
18719
- }
18720
- search(params2) {
18721
- return this.call("group.search", params2);
18722
- }
18723
- suspend(params2) {
18724
- return this.call("group.suspend", params2);
18725
- }
18726
- resume(params2) {
18727
- return this.call("group.resume", params2);
18728
- }
18729
- dissolve(params2) {
18730
- return this.call("group.dissolve", params2);
18731
- }
18732
- addMember(params2) {
18733
- return this.call("group.add_member", params2);
18734
- }
18735
- async inviteCall(method, params2) {
18736
- const normalized = normalizeInviteParams(params2);
18737
- try {
18738
- const result = await this.call(method, normalized);
18739
- await this.client._onGroupInviteActionResult?.(method, normalized, result);
18740
- return result;
18741
- } catch (error) {
18742
- await this.client._onGroupInviteActionResult?.(method, normalized, void 0, error);
18743
- throw error;
18744
- }
18745
- }
18746
- inviteMember(params2) {
18747
- return this.inviteCall("group.invite_member", params2);
18748
- }
18749
- listMyInvites(params2) {
18750
- return this.inviteCall("group.list_my_invites", params2);
18751
- }
18752
- acceptInvite(params2) {
18753
- return this.inviteCall("group.accept_invite", params2);
18754
- }
18755
- rejectInvite(params2) {
18756
- return this.inviteCall("group.reject_invite", params2);
18757
- }
18758
- revokeInvite(params2) {
18759
- return this.inviteCall("group.revoke_invite", params2);
18760
- }
18761
- getMembers(params2) {
18762
- return this.call("group.get_members", params2);
18763
- }
18764
- kick(params2) {
18765
- return this.call("group.kick", params2);
18766
- }
18767
- leave(params2) {
18768
- return this.call("group.leave", params2);
18769
- }
18770
- setRole(params2) {
18771
- return this.call("group.set_role", params2);
18772
- }
18773
- transferOwner(params2) {
18774
- const split = splitAidStore(params2);
18775
- if (split.aidStore && typeof this.client.startGroupTransfer === "function") {
18776
- return this.client.startGroupTransfer(split.params, { aidStore: split.aidStore });
18777
- }
18778
- return this.call("group.transfer_owner", split.params);
18779
- }
18780
- completeTransfer(params2) {
18781
- const split = splitAidStore(params2);
18782
- if (split.aidStore && typeof this.client.completeGroupTransfer === "function") {
18783
- return this.client.completeGroupTransfer(split.params, { aidStore: split.aidStore });
18784
- }
18785
- return this.call("group.complete_transfer", split.params);
19343
+ this.client = client;
18786
19344
  }
18787
- ban(params2) {
18788
- return this.call("group.ban", params2);
19345
+ call(method, params2) {
19346
+ return this.client.call(method, stripNil2(params2));
18789
19347
  }
18790
- unban(params2) {
18791
- return this.call("group.unban", params2);
19348
+ };
19349
+ var MessageThoughtFacade = class extends RpcFacade {
19350
+ put(params2) {
19351
+ return this.call("message.thought.put", params2);
18792
19352
  }
18793
- getBanlist(params2) {
18794
- return this.call("group.get_banlist", params2);
19353
+ get(params2) {
19354
+ return this.call("message.thought.get", params2);
18795
19355
  }
18796
- requestJoin(params2) {
18797
- return this.call("group.request_join", params2);
19356
+ };
19357
+ var MessageFacade = class extends RpcFacade {
19358
+ constructor() {
19359
+ super(...arguments);
19360
+ __publicField(this, "_thought");
18798
19361
  }
18799
- listJoinRequests(params2) {
18800
- return this.call("group.list_join_requests", params2);
19362
+ get thought() {
19363
+ if (!this._thought) this._thought = new MessageThoughtFacade(this.client);
19364
+ return this._thought;
18801
19365
  }
18802
- reviewJoinRequest(params2) {
18803
- return this.call("group.review_join_request", params2);
19366
+ send(params2) {
19367
+ return this.call("message.send", params2);
18804
19368
  }
18805
- batchReviewJoinRequest(params2) {
18806
- return this.call("group.batch_review_join_request", params2);
19369
+ pull(params2) {
19370
+ return this.call("message.pull", params2);
18807
19371
  }
18808
- createInviteCode(params2) {
18809
- return this.call("group.create_invite_code", params2);
19372
+ history(params2) {
19373
+ return this.call("message.history", params2);
18810
19374
  }
18811
- listInviteCodes(params2) {
18812
- return this.call("group.list_invite_codes", params2);
19375
+ ack(params2) {
19376
+ return this.call("message.ack", params2);
18813
19377
  }
18814
- useInviteCode(params2) {
18815
- return this.call("group.use_invite_code", params2);
19378
+ recall(params2) {
19379
+ return this.call("message.recall", params2);
18816
19380
  }
18817
- revokeInviteCode(params2) {
18818
- return this.call("group.revoke_invite_code", params2);
19381
+ queryOnline(params2) {
19382
+ return this.call("message.query_online", params2);
18819
19383
  }
18820
- setSettings(params2) {
18821
- return this.call("group.set_settings", params2);
19384
+ };
19385
+ function normalizeInviteParams(params2) {
19386
+ const out = stripNil2(params2);
19387
+ const rawGroupId = out.group_id ?? out.groupId ?? out.group_aid ?? out.groupAid;
19388
+ if (rawGroupId !== void 0) {
19389
+ out.group_id = normalizeGroupId(rawGroupId);
19390
+ delete out.groupId;
19391
+ delete out.group_aid;
19392
+ delete out.groupAid;
18822
19393
  }
18823
- getSettings(params2) {
18824
- return this.call("group.get_settings", params2);
19394
+ for (const [alias, canonical] of [["inviteId", "invite_id"], ["inviteeAid", "invitee_aid"]]) {
19395
+ if (out[canonical] === void 0 && out[alias] !== void 0) out[canonical] = out[alias];
19396
+ delete out[alias];
18825
19397
  }
18826
- async checkGroupIndex(params2) {
18827
- const merged = stripNil2(params2);
18828
- const groupAid = String(merged.group_aid ?? merged.group_id ?? "").trim();
18829
- if (!groupAid) throw new Error("group_aid is required");
18830
- const clientAny = this.client;
18831
- const stale = typeof clientAny.isGroupIndexStale === "function" ? Boolean(clientAny.isGroupIndexStale(groupAid)) : false;
18832
- const remoteMeta = typeof clientAny.getGroupIndexRemoteMeta === "function" ? clientAny.getGroupIndexRemoteMeta(groupAid) ?? {} : {};
18833
- const localEtag = typeof clientAny.getGroupIndexLocalEtag === "function" ? String(clientAny.getGroupIndexLocalEtag(groupAid) ?? "") : "";
18834
- const remoteEtag = String(remoteMeta?.etag ?? "");
18835
- const localFound = Boolean(localEtag);
18836
- const remoteFound = Boolean(remoteEtag);
18837
- const inSync = localFound && remoteFound && localEtag === remoteEtag;
18838
- return {
18839
- group_aid: groupAid,
18840
- local_found: localFound,
18841
- remote_found: remoteFound,
18842
- local_etag: localEtag,
18843
- remote_etag: remoteEtag,
18844
- in_sync: inSync,
18845
- needs_update: Boolean(stale || remoteFound && !inSync),
18846
- last_modified: remoteMeta?.last_modified,
18847
- status: remoteFound ? 200 : 404,
18848
- cached: true
18849
- };
19398
+ return out;
19399
+ }
19400
+ var MailFacade = class extends RpcFacade {
19401
+ send(params2) {
19402
+ return this.call("mail.send", params2);
18850
19403
  }
18851
- async getGroupIndex(params2) {
18852
- const merged = stripNil2(params2);
18853
- const groupId = String(merged.group_id ?? "").trim();
18854
- if (!groupId) throw new Error("group_id is required");
18855
- const result = await this.getSettings({ group_id: groupId, keys: [GROUP_INDEX_KEY] });
18856
- const resultMap = result;
18857
- const groupAid = String(resultMap.group_aid ?? "").trim();
18858
- if (!groupAid) throw new Error("group.get_settings response group_aid is required");
18859
- let groupIndex = null;
18860
- for (const item of result.settings ?? []) {
18861
- if (item?.key !== GROUP_INDEX_KEY) continue;
18862
- groupIndex = item.value;
18863
- break;
18864
- }
18865
- if (!groupIndex) {
18866
- return { group_id: resultMap.group_id, group_aid: groupAid, group_index: null, meta: {}, entries: [], settings: {} };
18867
- }
18868
- const parsed = parseGroupIndex(groupIndex);
18869
- this.validateGroupIndexEntries(parsed.entries);
18870
- await this.verifyPulledGroupIndex(groupId, groupAid, groupIndex, parsed);
18871
- const etag = String(parsed.meta.etag ?? "");
18872
- const settings = await this.hydrateGroupIndexSettings(groupId, groupAid, parsed.entries, etag, groupIndex);
18873
- return {
18874
- group_id: resultMap.group_id,
18875
- group_aid: groupAid,
18876
- group_index: groupIndex,
18877
- meta: parsed.meta,
18878
- entries: parsed.entries,
18879
- settings
18880
- };
19404
+ get(params2) {
19405
+ return this.call("mail.get", params2);
18881
19406
  }
18882
- async verifyPulledGroupIndex(groupId, groupAid, groupIndex, parsed) {
18883
- if (String(parsed.meta?.group_aid ?? "").trim() !== groupAid) throw new Error("group.index group_aid does not match group.get_settings response");
18884
- const signedBy = String(parsed.meta?.signed_by ?? "").trim();
18885
- if (!signedBy) throw new Error("group.index signed_by is required");
18886
- const certFingerprint = String(parsed.meta?.cert_fingerprint ?? "").trim();
18887
- const clientAny = this.client;
18888
- const currentAid = clientAny.currentAid;
18889
- let signer = currentAid?.aid === signedBy && !certFingerprint ? currentAid : null;
18890
- if (currentAid?.aid === signedBy && certFingerprint && typeof currentAid.certPem === "string" && currentAid.certPem) {
18891
- try {
18892
- if (await certMatchesFingerprint(currentAid.certPem, certFingerprint)) signer = currentAid;
18893
- } catch {
18894
- signer = null;
18895
- }
18896
- }
18897
- if (!signer && typeof clientAny.lookupPeer === "function") {
18898
- signer = await clientAny.lookupPeer(signedBy, certFingerprint || void 0);
18899
- }
18900
- if (!signer) throw new Error(`group.index signer is unavailable: ${signedBy}`);
18901
- const verified = await verifyGroupIndex(groupIndex, signer);
18902
- if (!verified.ok) throw new Error(verified.error.message || "group.index verification failed");
18903
- if (!verified.data.valid) throw new Error(`group.index verification failed: ${verified.data.reason || "invalid signature"}`);
18904
- const adminsResult = await this.call("group.get_admins", { group_id: groupId });
18905
- const adminsMap = adminsResult;
18906
- if (String(adminsMap.group_aid ?? "").trim() !== groupAid) throw new Error("group.get_admins response group_aid does not match group.get_settings response");
18907
- const authorized = Array.isArray(adminsMap.admins) && adminsMap.admins.some(
18908
- (item) => String(item?.aid ?? "").trim() === signedBy && ["owner", "admin"].includes(String(item?.role ?? "").trim().toLowerCase())
18909
- );
18910
- if (!authorized) throw new Error(`group.index signer is not a current owner or admin: ${signedBy}`);
19407
+ list(params2) {
19408
+ return this.call("mail.list", params2);
18911
19409
  }
18912
- async updateGroupIndex(params2) {
18913
- const merged = stripNil2(params2);
18914
- const groupId = String(merged.group_id ?? "").trim();
18915
- const settings = merged.settings;
18916
- if (!groupId) throw new Error("group_id is required");
18917
- if (!settings || typeof settings !== "object" || Array.isArray(settings) || Object.keys(settings).length === 0) {
18918
- throw new Error("settings must be a non-empty object");
18919
- }
18920
- const signer = merged.signer ?? this.client.currentAid;
18921
- if (!signer) throw new Error("signer is required");
18922
- const lastModified = Math.trunc(Number(merged.last_modified ?? Date.now()));
18923
- const maxAttempts = Math.max(1, Math.trunc(Number(merged.max_attempts ?? 2)));
18924
- let lastError = null;
18925
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
18926
- const current = await this.getGroupIndex({ group_id: groupId });
18927
- const groupAid = String(current.group_aid ?? "").trim();
18928
- const currentIndex = current.group_index ?? null;
18929
- const expectedEtag = currentIndex ? String(parseGroupIndex(currentIndex).meta.etag ?? "") : "";
18930
- const baseSettings = current.settings && typeof current.settings === "object" && !Array.isArray(current.settings) ? current.settings : {};
18931
- const signedSettings = await prepareGroupSettingsWithIndex({
18932
- groupAid,
18933
- settings,
18934
- signer,
18935
- lastModified,
18936
- baseIndex: currentIndex
18937
- });
18938
- try {
18939
- const result = await this.setSettings({
18940
- group_id: groupId,
18941
- settings: signedSettings,
18942
- expected_index_etag: expectedEtag
18943
- });
18944
- const pushedEtag = String(parseGroupIndex(signedSettings[GROUP_INDEX_KEY]).meta.etag ?? "");
18945
- const clientAny = this.client;
18946
- if (typeof clientAny.cacheGroupIndexSettings === "function") {
18947
- const parsed = parseGroupIndex(signedSettings[GROUP_INDEX_KEY]);
18948
- await clientAny.cacheGroupIndexSettings(groupAid, { ...baseSettings, ...settings }, {
18949
- entries: parsed.entries,
18950
- etag: pushedEtag,
18951
- groupIndex: signedSettings[GROUP_INDEX_KEY]
18952
- });
18953
- }
18954
- return result;
18955
- } catch (exc) {
18956
- if (!String(exc?.message ?? exc).includes("etag conflict")) throw exc;
18957
- lastError = exc;
18958
- }
18959
- }
18960
- if (lastError) throw lastError;
18961
- throw new Error("updateGroupIndex failed");
19410
+ delete(params2) {
19411
+ return this.call("mail.delete", params2);
18962
19412
  }
18963
- async getIndexedSettings(groupId, keys) {
18964
- const clientAny = this.client;
18965
- if (typeof clientAny.getGroupIndexCachedSettings === "function") {
18966
- const cached = await clientAny.getGroupIndexCachedSettings(groupId, keys);
18967
- if (cached && typeof cached === "object") return { groupId, settings: cached };
18968
- }
18969
- const result = await this.getGroupIndex({ group_id: groupId });
18970
- const resultMap = result;
18971
- const allSettings = resultMap.settings && typeof resultMap.settings === "object" && !Array.isArray(resultMap.settings) ? resultMap.settings : {};
18972
- const settings = Object.fromEntries(keys.filter((key) => key in allSettings).map((key) => [key, allSettings[key]]));
18973
- const groupAid = String(resultMap.group_aid ?? "").trim();
18974
- if (groupAid && groupAid !== groupId && typeof clientAny.cacheGroupIndexSettings === "function" && resultMap.group_index) {
18975
- await clientAny.cacheGroupIndexSettings(groupId, allSettings, { entries: resultMap.entries, etag: String(resultMap.meta?.etag ?? ""), groupIndex: resultMap.group_index });
18976
- }
18977
- return { groupId: String(resultMap.group_id ?? groupId), settings };
19413
+ move(params2) {
19414
+ return this.call("mail.move", params2);
18978
19415
  }
18979
- async hydrateGroupIndexSettings(groupId, groupAid, entries, etag, groupIndex) {
18980
- const keys = entries.map((item) => String(item.key));
18981
- const clientAny = this.client;
18982
- const settings = {};
18983
- if (keys.length > 0) {
18984
- const fetchedResult = await this.getSettings({ group_id: groupId, keys });
18985
- const fetchedMap = fetchedResult;
18986
- if (String(fetchedMap.group_aid ?? "").trim() !== groupAid) throw new Error("group settings response group_aid does not match verified group.index");
18987
- const seen = /* @__PURE__ */ new Set();
18988
- for (const item of fetchedMap.settings ?? []) {
18989
- const key = String(item?.key ?? "").trim();
18990
- if (!key || seen.has(key)) throw new Error(`group settings response contains duplicate or empty key: ${key}`);
18991
- seen.add(key);
18992
- settings[key] = item?.value;
18993
- }
18994
- for (const entry of entries) {
18995
- const key = String(entry.key);
18996
- if (!seen.has(key)) throw new Error(`group.index setting is missing: ${key}`);
18997
- if (await groupIndexSettingEtag(settings[key]) !== String(entry.etag)) throw new Error(`group.index setting etag mismatch: ${key}`);
18998
- }
18999
- }
19000
- if (typeof clientAny.cacheGroupIndexSettings === "function") {
19001
- await clientAny.cacheGroupIndexSettings(groupAid, settings, { entries, etag, groupIndex });
19002
- }
19003
- return settings;
19416
+ mark(params2) {
19417
+ return this.call("mail.mark", params2);
19418
+ }
19419
+ search(params2) {
19420
+ return this.call("mail.search", params2);
19004
19421
  }
19005
- validateGroupIndexEntries(entries) {
19006
- const seen = /* @__PURE__ */ new Set();
19007
- for (const entry of entries) {
19008
- const key = String(entry?.key ?? "").trim();
19009
- const source = String(entry?.source ?? "").trim();
19010
- const etag = String(entry?.etag ?? "").trim();
19011
- if (!key) throw new Error("group.index entry key is required");
19012
- if (seen.has(key)) throw new Error(`group.index contains duplicate key: ${key}`);
19013
- if (source !== "db") throw new Error(`group.index entry source is unsupported: ${source || "<empty>"}`);
19014
- if (!etag) throw new Error(`group.index entry etag is required: ${key}`);
19015
- seen.add(key);
19016
- }
19422
+ getAttachment(params2) {
19423
+ return this.call("mail.get_attachment", params2);
19017
19424
  }
19018
- send(params2) {
19019
- validateGroupIDFormat(params2?.group_id, "group_id");
19020
- return this.call("group.send", params2);
19425
+ getQuota(params2) {
19426
+ return this.call("mail.get_quota", params2);
19021
19427
  }
19022
- recall(params2) {
19023
- return this.call("group.recall", params2);
19428
+ health(params2) {
19429
+ return this.call("mail.health", params2);
19024
19430
  }
19025
- pull(params2) {
19026
- validateGroupIDFormat(params2?.group_id, "group_id");
19027
- return this.call("group.pull", params2);
19431
+ status(params2) {
19432
+ return this.call("mail.status", params2);
19028
19433
  }
19029
- history(params2) {
19030
- return this.call("group.history", params2);
19434
+ };
19435
+ var GroupThoughtFacade = class extends RpcFacade {
19436
+ put(params2) {
19437
+ return this.call("group.thought.put", params2);
19031
19438
  }
19032
- pullEvents(params2) {
19033
- return this.call("group.pull_events", params2);
19439
+ get(params2) {
19440
+ return this.call("group.thought.get", params2);
19034
19441
  }
19035
- ackMessages(params2) {
19036
- return this.call("group.ack_messages", params2);
19442
+ };
19443
+ var GroupFacade = class extends RpcFacade {
19444
+ constructor() {
19445
+ super(...arguments);
19446
+ __publicField(this, "_fs");
19447
+ __publicField(this, "_thought");
19037
19448
  }
19038
- ackEvents(params2) {
19039
- return this.call("group.ack_events", params2);
19449
+ get fs() {
19450
+ if (!this._fs) this._fs = new GroupFSVFS(this.client);
19451
+ return this._fs;
19040
19452
  }
19041
- documentSettingResult(groupId, keyName, settings) {
19042
- const contentKey = `${keyName}.content`;
19043
- const attachmentsKey = `${keyName}.attachments`;
19044
- return {
19045
- group_id: groupId,
19046
- setting: {
19047
- group_id: groupId,
19048
- key_name: keyName,
19049
- content: settings[contentKey] ?? "",
19050
- attachments: settings[attachmentsKey] ?? [],
19051
- updated_by: settings[`${contentKey}.updated_by`] ?? "",
19052
- updated_at: settings[`${contentKey}.updated_at`] ?? 0
19053
- }
19054
- };
19453
+ get thought() {
19454
+ if (!this._thought) this._thought = new GroupThoughtFacade(this.client);
19455
+ return this._thought;
19055
19456
  }
19056
- async getSettingWithIndex(params2) {
19057
- const merged = params2 || {};
19058
- const groupId = merged.group_id;
19059
- if (!groupId) throw new Error("group_id is required");
19060
- const keyName = indexedDocumentKeyName(merged);
19061
- const { groupId: resultGroupId, settings } = await this.getIndexedSettings(
19062
- String(groupId),
19063
- [`${keyName}.content`, `${keyName}.attachments`]
19064
- );
19065
- return this.documentSettingResult(resultGroupId, keyName, settings);
19457
+ create(params2) {
19458
+ const split = splitAidStore(params2);
19459
+ if (split.params.group_name === void 0 && split.params.groupName !== void 0) {
19460
+ split.params.group_name = split.params.groupName;
19461
+ delete split.params.groupName;
19462
+ }
19463
+ if (split.aidStore && typeof this.client.createGroup === "function") {
19464
+ return this.client.createGroup(split.params, { aidStore: split.aidStore });
19465
+ }
19466
+ return this.call("group.create", split.params);
19066
19467
  }
19067
- async updateSettingWithIndex(params2) {
19068
- const merged = params2 || {};
19069
- const groupId = merged.group_id;
19070
- if (!groupId) throw new Error("group_id is required");
19071
- const keyName = indexedDocumentKeyName(merged);
19072
- if (!("content" in merged)) throw new Error("content is required");
19073
- const content = merged.content;
19074
- const attachments = merged.attachments ?? [];
19075
- const settingsUpdate = { [`${keyName}.content`]: content };
19076
- if ("attachments" in merged) {
19077
- settingsUpdate[`${keyName}.attachments`] = attachments;
19468
+ bindAid(params2) {
19469
+ return this.call("group.bind_aid", splitAidStore(params2).params);
19470
+ }
19471
+ bindGroupAid(params2) {
19472
+ const split = splitAidStore(params2);
19473
+ if (split.aidStore && typeof this.client.bindGroupAid === "function") {
19474
+ return this.client.bindGroupAid(split.params, { aidStore: split.aidStore });
19078
19475
  }
19079
- const result = await this.updateGroupIndex(indexUpdateParams(groupId, settingsUpdate, merged));
19080
- const resultGroupId = String(result.group_id ?? groupId);
19081
- return {
19082
- group_id: resultGroupId,
19083
- setting: {
19084
- group_id: resultGroupId,
19085
- key_name: keyName,
19086
- content,
19087
- attachments,
19088
- updated_by: "",
19089
- updated_at: 0
19090
- }
19091
- };
19476
+ return this.call("group.bind_group_aid", split.params);
19092
19477
  }
19093
- async getAnnouncement(params2) {
19094
- const merged = params2 || {};
19095
- const result = await this.getSettingWithIndex({ ...merged, keyName: "announcement" });
19096
- const resultMap = result;
19097
- const setting = resultMap.setting;
19098
- return {
19099
- group_id: resultMap.group_id,
19100
- announcement: {
19101
- group_id: setting.group_id,
19102
- content: setting.content,
19103
- attachments: setting.attachments,
19104
- updated_by: setting.updated_by,
19105
- updated_at: setting.updated_at
19106
- }
19107
- };
19478
+ renewGroupAid(params2) {
19479
+ const split = splitAidStore(params2);
19480
+ if (typeof this.client.renewGroupAid === "function") {
19481
+ return this.client.renewGroupAid(split.params, { aidStore: split.aidStore });
19482
+ }
19483
+ return this.call("group.renew_group_aid", split.params);
19108
19484
  }
19109
- async updateAnnouncement(params2) {
19110
- const merged = params2 || {};
19111
- const result = await this.updateSettingWithIndex({ ...merged, keyName: "announcement" });
19112
- const resultMap = result;
19113
- const setting = resultMap.setting;
19114
- return {
19115
- group_id: resultMap.group_id,
19116
- announcement: {
19117
- group_id: resultMap.group_id,
19118
- content: setting.content,
19119
- attachments: setting.attachments
19120
- }
19121
- };
19485
+ getInfo(params2) {
19486
+ return this.call("group.get_info", params2);
19122
19487
  }
19123
- async getRules(params2) {
19124
- const merged = params2 || {};
19125
- const result = await this.getSettingWithIndex({ ...merged, keyName: "rules" });
19126
- const resultMap = result;
19127
- const setting = resultMap.setting;
19128
- return {
19129
- group_id: resultMap.group_id,
19130
- rules: {
19131
- group_id: setting.group_id,
19132
- content: setting.content,
19133
- attachments: setting.attachments,
19134
- updated_by: setting.updated_by,
19135
- updated_at: setting.updated_at
19136
- }
19137
- };
19488
+ update(params2) {
19489
+ return this.call("group.update", params2);
19138
19490
  }
19139
- async updateRules(params2) {
19140
- const merged = params2 || {};
19141
- const result = await this.updateSettingWithIndex({ ...merged, keyName: "rules" });
19142
- const resultMap = result;
19143
- const setting = resultMap.setting;
19144
- return {
19145
- group_id: resultMap.group_id,
19146
- rules: {
19147
- group_id: resultMap.group_id,
19148
- content: setting.content,
19149
- attachments: setting.attachments
19150
- }
19151
- };
19491
+ list(params2) {
19492
+ return this.call("group.list", params2);
19152
19493
  }
19153
- async getJoinRequirements(params2) {
19154
- const merged = params2 || {};
19155
- const groupId = merged.group_id;
19156
- if (!groupId) throw new Error("group_id is required");
19157
- const { groupId: resultGroupId, settings } = await this.getIndexedSettings(
19158
- String(groupId),
19159
- ["join.mode", "join.question", "join.auto_approve_patterns", "join.max_pending", "join.attachments"]
19160
- );
19161
- return {
19162
- group_id: resultGroupId,
19163
- join_requirements: {
19164
- group_id: resultGroupId,
19165
- mode: settings["join.mode"] || "open",
19166
- question: settings["join.question"] || "",
19167
- auto_approve_patterns: settings["join.auto_approve_patterns"] || [],
19168
- max_pending: settings["join.max_pending"] || 100,
19169
- attachments: settings["join.attachments"] || [],
19170
- updated_by: settings["join.mode.updated_by"] || "",
19171
- updated_at: settings["join.mode.updated_at"] || 0
19172
- }
19173
- };
19494
+ listMy(params2) {
19495
+ return this.call("group.list_my", params2);
19174
19496
  }
19175
- async updateJoinRequirements(params2) {
19176
- const merged = params2 || {};
19177
- const groupId = merged.group_id;
19178
- if (!groupId) throw new Error("group_id is required");
19179
- const settingsUpdate = {};
19180
- if ("mode" in merged) settingsUpdate["join.mode"] = merged.mode;
19181
- if ("question" in merged) settingsUpdate["join.question"] = merged.question;
19182
- if ("auto_approve_patterns" in merged) settingsUpdate["join.auto_approve_patterns"] = merged.auto_approve_patterns;
19183
- if ("max_pending" in merged) settingsUpdate["join.max_pending"] = merged.max_pending;
19184
- if ("attachments" in merged) settingsUpdate["join.attachments"] = merged.attachments;
19185
- if (Object.keys(settingsUpdate).length === 0) {
19186
- throw new Error("at least one field to update is required");
19187
- }
19188
- const result = await this.updateGroupIndex(indexUpdateParams(groupId, settingsUpdate, merged));
19189
- return {
19190
- group_id: result.group_id,
19191
- join_requirements: {
19192
- group_id: result.group_id,
19193
- mode: merged.mode,
19194
- question: merged.question,
19195
- auto_approve_patterns: merged.auto_approve_patterns,
19196
- max_pending: merged.max_pending,
19197
- attachments: merged.attachments || []
19198
- }
19199
- };
19497
+ search(params2) {
19498
+ return this.call("group.search", params2);
19200
19499
  }
19201
- getOnlineMembers(params2) {
19202
- return this.call("group.get_online_members", params2);
19500
+ suspend(params2) {
19501
+ return this.call("group.suspend", params2);
19203
19502
  }
19204
- };
19205
- var StreamFacade = class extends RpcFacade {
19206
- create(params2) {
19207
- return this.call("stream.create", params2);
19503
+ resume(params2) {
19504
+ return this.call("group.resume", params2);
19505
+ }
19506
+ dissolve(params2) {
19507
+ return this.call("group.dissolve", params2);
19508
+ }
19509
+ addMember(params2) {
19510
+ return this.call("group.add_member", params2);
19208
19511
  }
19209
- close(params2) {
19210
- return this.call("stream.close", params2);
19512
+ async inviteCall(method, params2) {
19513
+ const normalized = normalizeInviteParams(params2);
19514
+ try {
19515
+ const result = await this.call(method, normalized);
19516
+ await this.client._onGroupInviteActionResult?.(method, normalized, result);
19517
+ return result;
19518
+ } catch (error) {
19519
+ await this.client._onGroupInviteActionResult?.(method, normalized, void 0, error);
19520
+ throw error;
19521
+ }
19211
19522
  }
19212
- getInfo(params2) {
19213
- return this.call("stream.get_info", params2);
19523
+ inviteMember(params2) {
19524
+ return this.inviteCall("group.invite_member", params2);
19214
19525
  }
19215
- listActive(params2) {
19216
- return this.call("stream.list_active", params2);
19526
+ listMyInvites(params2) {
19527
+ return this.inviteCall("group.list_my_invites", params2);
19217
19528
  }
19218
- };
19219
-
19220
- // src/storage/types.ts
19221
- function text2(value, fallback = "") {
19222
- if (value === null || value === void 0) return fallback;
19223
- return String(value);
19224
- }
19225
- function intValue(value, fallback = 0) {
19226
- const parsed = Number(value);
19227
- return Number.isFinite(parsed) ? Math.trunc(parsed) : fallback;
19228
- }
19229
- function boolValue(value, fallback = false) {
19230
- if (value === null || value === void 0) return fallback;
19231
- return Boolean(value);
19232
- }
19233
- function mountSourceFromRaw(raw) {
19234
- const explicit = text2(raw.mount_source);
19235
- if (explicit) return explicit;
19236
- const sourceAid = text2(raw.source_aid);
19237
- if (!sourceAid) return "";
19238
- return `${sourceAid}:${keyToPath(raw.source_path)}`;
19239
- }
19240
- function normalizePath(path) {
19241
- let raw = String(path || "/").replace(/\\/g, "/").trim();
19242
- if (!raw.startsWith("/")) raw = `/${raw}`;
19243
- raw = raw.replace(/\/+/g, "/");
19244
- const parts = [];
19245
- for (const part of raw.split("/")) {
19246
- if (!part || part === ".") continue;
19247
- if (part === "..") {
19248
- parts.pop();
19249
- continue;
19250
- }
19251
- parts.push(part);
19529
+ acceptInvite(params2) {
19530
+ return this.inviteCall("group.accept_invite", params2);
19252
19531
  }
19253
- return parts.length ? `/${parts.join("/")}` : "/";
19254
- }
19255
- function pathToKey(path) {
19256
- const normalized = normalizePath(path);
19257
- return normalized === "/" ? "" : normalized.slice(1);
19258
- }
19259
- function keyToPath(key) {
19260
- return normalizePath(text2(key));
19261
- }
19262
- function nameFromPath(path) {
19263
- const cleaned = normalizePath(path).replace(/\/+$/g, "");
19264
- if (!cleaned || cleaned === "/") return "/";
19265
- return cleaned.split("/").pop() || "/";
19266
- }
19267
- function baseNode(raw, type, path) {
19268
- return {
19269
- type,
19270
- path,
19271
- name: text2(raw.name) || nameFromPath(path),
19272
- owner: text2(raw.owner ?? raw.owner_aid),
19273
- bucket: text2(raw.bucket, "default") || "default",
19274
- size: intValue(raw.size ?? raw.size_bytes),
19275
- mtime: intValue(raw.mtime ?? raw.updated_at),
19276
- contentType: text2(raw.content_type),
19277
- version: intValue(raw.version),
19278
- mode: text2(raw.mode),
19279
- isPublic: !boolValue(raw.is_private, true),
19280
- objectId: text2(raw.object_id),
19281
- folderId: text2(raw.folder_id),
19282
- target: text2(raw.target),
19283
- mountSource: mountSourceFromRaw(raw),
19284
- metadata: raw.metadata && typeof raw.metadata === "object" && !Array.isArray(raw.metadata) ? raw.metadata : {}
19285
- };
19286
- }
19287
- function nodeFromAny(input) {
19288
- const raw = input && typeof input === "object" ? input : {};
19289
- const nodeType = text2(raw.type ?? raw.node_type).toLowerCase();
19290
- if (["folder", "dir", "directory"].includes(nodeType)) return baseNode(raw, "dir", keyToPath(raw.path));
19291
- if (["symlink", "link"].includes(nodeType)) return baseNode(raw, "symlink", keyToPath(raw.path));
19292
- if (nodeType === "mount") return baseNode(raw, "mount", keyToPath(raw.path));
19293
- const path = keyToPath(raw.path ?? raw.object_key);
19294
- return {
19295
- ...baseNode(raw, "file", path),
19296
- sha256: text2(raw.sha256),
19297
- etag: text2(raw.etag)
19298
- };
19299
- }
19300
- function usageFromAny(input, ownerFallback = "") {
19301
- const raw = input && typeof input === "object" ? input : {};
19302
- const quota = intValue(raw.quota_bytes ?? raw.quota_total_bytes);
19303
- const used = intValue(raw.used_bytes ?? raw.quota_used_bytes);
19304
- return {
19305
- owner: text2(raw.owner ?? raw.owner_aid, ownerFallback),
19306
- quotaBytes: quota,
19307
- usedBytes: used,
19308
- availBytes: quota ? Math.max(0, quota - used) : 0,
19309
- objectCount: intValue(raw.object_count)
19310
- };
19311
- }
19312
- function unmountFromAny(input, fallback) {
19313
- const raw = input && typeof input === "object" ? input : {};
19314
- const path = keyToPath(raw.mount_path ?? raw.path ?? fallback.mountPath);
19315
- return {
19316
- unmounted: boolValue(raw.unmounted),
19317
- owner: text2(raw.owner ?? raw.owner_aid, fallback.owner ?? ""),
19318
- bucket: text2(raw.bucket, fallback.bucket ?? "default") || (fallback.bucket ?? "default"),
19319
- path,
19320
- mountPath: path
19321
- };
19322
- }
19323
-
19324
- // src/storage/vfs.ts
19325
- async function sha256Hex3(data) {
19326
- const copy = new Uint8Array(data.byteLength);
19327
- copy.set(data);
19328
- const subtle = globalThis.crypto?.subtle;
19329
- if (!subtle?.digest) throw new StorageError("SHA-256 unavailable", "EUNSUPPORTED");
19330
- const digest = await subtle.digest("SHA-256", copy.buffer);
19331
- return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
19332
- }
19333
- function toBytes2(data) {
19334
- if (typeof data === "string") return new TextEncoder().encode(data);
19335
- if (data instanceof Uint8Array) return data;
19336
- return new Uint8Array(data);
19337
- }
19338
- function textValue(value) {
19339
- if (value === void 0 || value === null) return "";
19340
- return String(value).trim();
19341
- }
19342
- function aidIssuer(aid) {
19343
- const value = textValue(aid).replace(/^\.+|\.+$/g, "").toLowerCase();
19344
- const separator = value.indexOf(".");
19345
- return separator >= 0 ? value.slice(separator + 1).replace(/^\.+|\.+$/g, "") : "";
19346
- }
19347
- function accessTokenFromClient2(client) {
19348
- const auth = client;
19349
- return textValue(
19350
- auth.accessToken ?? auth.access_token ?? auth._identity?.access_token ?? auth._sessionParams?.access_token ?? auth._session_params?.access_token
19351
- );
19352
- }
19353
- function isInlineFallbackError(error) {
19354
- const code = String(error.code).toUpperCase();
19355
- if (["EINLINE", "EINLINE_LIMIT", "ERR_INLINE_LIMIT", "INLINE_LIMIT", "-32033"].includes(code)) return true;
19356
- return error.code === -32602 && error.message.toLowerCase().includes("inline");
19357
- }
19358
- function parsePathLike(input, fallbackOwner) {
19359
- if (typeof input !== "string") {
19360
- return { owner: input.owner ?? fallbackOwner ?? null, path: input.path };
19532
+ rejectInvite(params2) {
19533
+ return this.inviteCall("group.reject_invite", params2);
19361
19534
  }
19362
- const match = input.match(/^([^:]+):(\/.*)$/);
19363
- if (match) {
19364
- return { owner: match[1], path: match[2] };
19535
+ revokeInvite(params2) {
19536
+ return this.inviteCall("group.revoke_invite", params2);
19365
19537
  }
19366
- return { owner: fallbackOwner ?? null, path: input };
19367
- }
19368
- var StorageVFS = class {
19369
- constructor(client, options = {}) {
19370
- __publicField(this, "lowlevel");
19371
- __publicField(this, "useFsRpc");
19372
- __publicField(this, "client");
19373
- this.client = client;
19374
- this.lowlevel = options.lowlevel ?? new StorageLowLevel(client);
19375
- this.useFsRpc = options.useFsRpc ?? true;
19538
+ getMembers(params2) {
19539
+ return this.call("group.get_members", params2);
19376
19540
  }
19377
- get defaultOwner() {
19378
- return this.client.aid ?? null;
19541
+ kick(params2) {
19542
+ return this.call("group.kick", params2);
19379
19543
  }
19380
- owner(owner) {
19381
- return owner || this.defaultOwner;
19544
+ leave(params2) {
19545
+ return this.call("group.leave", params2);
19382
19546
  }
19383
- downloadUrlAndHeaders(ticket, owner) {
19384
- const url = textValue(ticket.logical_url ?? ticket.download_url ?? ticket.url);
19385
- const currentAid = textValue(this.defaultOwner);
19386
- const targetAid = textValue(ticket.owner_aid ?? owner);
19387
- const currentIssuer = aidIssuer(currentAid);
19388
- const targetIssuer = aidIssuer(targetAid);
19389
- const sameIssuer = Boolean(currentAid && targetAid && (currentAid.toLowerCase() === targetAid.toLowerCase() || currentIssuer && targetIssuer && currentIssuer === targetIssuer));
19390
- const token = sameIssuer ? accessTokenFromClient2(this.client) : "";
19391
- return { url, headers: token ? { Authorization: `Bearer ${token}` } : void 0 };
19547
+ setRole(params2) {
19548
+ return this.call("group.set_role", params2);
19392
19549
  }
19393
- async writeBytes(path, input, options = {}) {
19394
- const data = toBytes2(input);
19395
- const owner = this.owner(options.owner);
19396
- const bucket = options.bucket ?? "default";
19397
- const objectKey = pathToKey(path);
19398
- const sha2566 = await sha256Hex3(data);
19399
- const overwrite = options.overwrite ?? false;
19400
- try {
19401
- const check = await this.lowlevel.checkUpload({ owner, bucket, objectKey, size: data.length, sha256: sha2566 });
19402
- if (check.within_limit === false) {
19403
- throw new StorageError(`file size exceeds max_file_size_bytes: ${data.length}`, "E2BIG", path, check);
19404
- }
19405
- if (check.target_exists && !overwrite && options.expectedVersion === void 0) {
19406
- throw new StorageExistsError(`remote path already exists: ${path}`, "EEXIST", path, check.target);
19407
- }
19408
- if (check.dedup_hit || check.skip_upload) {
19409
- const completed = await this.lowlevel.completeUpload({
19410
- owner,
19411
- bucket,
19412
- objectKey,
19413
- size: data.length,
19414
- sha256: sha2566,
19415
- contentType: options.contentType ?? "application/octet-stream",
19416
- metadata: options.metadata,
19417
- isPublic: options.public ?? false,
19418
- expectedVersion: options.expectedVersion,
19419
- skipBlob: true,
19420
- overwrite
19421
- });
19422
- return nodeFromAny(completed);
19423
- }
19424
- if (check.inline === true) {
19425
- return nodeFromAny(await this.lowlevel.putObject({
19426
- owner,
19427
- bucket,
19428
- objectKey,
19429
- content: data,
19430
- contentType: options.contentType ?? "application/octet-stream",
19431
- metadata: options.metadata,
19432
- isPublic: options.public ?? false,
19433
- expectedVersion: options.expectedVersion,
19434
- overwrite
19435
- }));
19436
- }
19437
- const session = await this.lowlevel.createUploadSession({
19438
- owner,
19439
- bucket,
19440
- objectKey,
19441
- size: data.length,
19442
- contentType: options.contentType,
19443
- expectedVersion: options.expectedVersion,
19444
- overwrite
19445
- });
19446
- const uploadUrl = String(session.upload_url ?? "");
19447
- if (!uploadUrl) throw new StorageError(`create_upload_session did not return upload_url`, "ESTORAGE", path);
19448
- await this.lowlevel.httpPut(uploadUrl, data, session.headers);
19449
- return nodeFromAny(await this.lowlevel.completeUpload({
19450
- owner,
19451
- bucket,
19452
- objectKey,
19453
- sessionId: String(session.session_id ?? ""),
19454
- size: data.length,
19455
- sha256: sha2566,
19456
- contentType: options.contentType,
19457
- metadata: options.metadata,
19458
- isPublic: options.public ?? false,
19459
- expectedVersion: options.expectedVersion,
19460
- overwrite
19461
- }));
19462
- } catch (exc) {
19463
- throw mapStorageError(exc, path);
19550
+ transferOwner(params2) {
19551
+ const split = splitAidStore(params2);
19552
+ if (split.aidStore && typeof this.client.startGroupTransfer === "function") {
19553
+ return this.client.startGroupTransfer(split.params, { aidStore: split.aidStore });
19554
+ }
19555
+ return this.call("group.transfer_owner", split.params);
19556
+ }
19557
+ completeTransfer(params2) {
19558
+ const split = splitAidStore(params2);
19559
+ if (split.aidStore && typeof this.client.completeGroupTransfer === "function") {
19560
+ return this.client.completeGroupTransfer(split.params, { aidStore: split.aidStore });
19464
19561
  }
19562
+ return this.call("group.complete_transfer", split.params);
19465
19563
  }
19466
- async readBytes(path, options = {}) {
19467
- return (await this.readBytesWithMetadata(path, options)).data;
19564
+ ban(params2) {
19565
+ return this.call("group.ban", params2);
19468
19566
  }
19469
- async readBytesWithMetadata(path, options = {}) {
19470
- const owner = this.owner(options.owner);
19471
- const bucket = options.bucket ?? "default";
19472
- const objectKey = pathToKey(path);
19473
- try {
19474
- const result = await this.lowlevel.getObject({ owner, bucket, objectKey, token: options.token, offset: options.offset, limit: options.limit });
19475
- return {
19476
- data: base64ToBytes(String(result.content ?? "")),
19477
- sha256: textValue(result.sha256)
19478
- };
19479
- } catch (exc) {
19480
- const mapped = mapStorageError(exc, path);
19481
- if (options.offset !== void 0 || options.limit !== void 0) throw mapped;
19482
- if (!isInlineFallbackError(mapped)) throw mapped;
19483
- }
19484
- const ticket = await this.lowlevel.createDownloadTicket({ owner, bucket, objectKey, token: options.token });
19485
- const { url: downloadUrl, headers } = this.downloadUrlAndHeaders(ticket, owner);
19486
- if (!downloadUrl) throw new StorageError(`create_download_ticket did not return download_url`, "ESTORAGE", path);
19567
+ unban(params2) {
19568
+ return this.call("group.unban", params2);
19569
+ }
19570
+ getBanlist(params2) {
19571
+ return this.call("group.get_banlist", params2);
19572
+ }
19573
+ block(params2) {
19574
+ return this.call("group.block", params2);
19575
+ }
19576
+ unblock(params2) {
19577
+ return this.call("group.unblock", params2);
19578
+ }
19579
+ getBlocklist(params2) {
19580
+ return this.call("group.get_blocklist", params2);
19581
+ }
19582
+ reportStatus(params2) {
19583
+ return this.call("group.report_status", params2);
19584
+ }
19585
+ getMemberStatuses(params2) {
19586
+ return this.call("group.get_member_statuses", params2);
19587
+ }
19588
+ requestJoin(params2) {
19589
+ return this.call("group.request_join", params2);
19590
+ }
19591
+ listJoinRequests(params2) {
19592
+ return this.call("group.list_join_requests", params2);
19593
+ }
19594
+ reviewJoinRequest(params2) {
19595
+ return this.call("group.review_join_request", params2);
19596
+ }
19597
+ batchReviewJoinRequest(params2) {
19598
+ return this.call("group.batch_review_join_request", params2);
19599
+ }
19600
+ createInviteCode(params2) {
19601
+ return this.call("group.create_invite_code", params2);
19602
+ }
19603
+ listInviteCodes(params2) {
19604
+ return this.call("group.list_invite_codes", params2);
19605
+ }
19606
+ useInviteCode(params2) {
19607
+ return this.call("group.use_invite_code", params2);
19608
+ }
19609
+ revokeInviteCode(params2) {
19610
+ return this.call("group.revoke_invite_code", params2);
19611
+ }
19612
+ setSettings(params2) {
19613
+ return this.call("group.set_settings", params2);
19614
+ }
19615
+ getSettings(params2) {
19616
+ return this.call("group.get_settings", params2);
19617
+ }
19618
+ async checkGroupIndex(params2) {
19619
+ const merged = stripNil2(params2);
19620
+ const groupAid = String(merged.group_aid ?? merged.group_id ?? "").trim();
19621
+ if (!groupAid) throw new Error("group_aid is required");
19622
+ const clientAny = this.client;
19623
+ const stale = typeof clientAny.isGroupIndexStale === "function" ? Boolean(clientAny.isGroupIndexStale(groupAid)) : false;
19624
+ const remoteMeta = typeof clientAny.getGroupIndexRemoteMeta === "function" ? clientAny.getGroupIndexRemoteMeta(groupAid) ?? {} : {};
19625
+ const localEtag = typeof clientAny.getGroupIndexLocalEtag === "function" ? String(clientAny.getGroupIndexLocalEtag(groupAid) ?? "") : "";
19626
+ const remoteEtag = String(remoteMeta?.etag ?? "");
19627
+ const localFound = Boolean(localEtag);
19628
+ const remoteFound = Boolean(remoteEtag);
19629
+ const inSync = localFound && remoteFound && localEtag === remoteEtag;
19487
19630
  return {
19488
- data: headers ? await this.lowlevel.httpGet(downloadUrl, headers) : await this.lowlevel.httpGet(downloadUrl),
19489
- sha256: textValue(ticket.sha256)
19631
+ group_aid: groupAid,
19632
+ local_found: localFound,
19633
+ remote_found: remoteFound,
19634
+ local_etag: localEtag,
19635
+ remote_etag: remoteEtag,
19636
+ in_sync: inSync,
19637
+ needs_update: Boolean(stale || remoteFound && !inSync),
19638
+ last_modified: remoteMeta?.last_modified,
19639
+ status: remoteFound ? 200 : 404,
19640
+ cached: true
19490
19641
  };
19491
19642
  }
19492
- async downloadFile(path, options = {}) {
19493
- const { data, sha256: expectedSha } = await this.readBytesWithMetadata(path, options);
19494
- const actualSha = await sha256Hex3(data);
19495
- const expected = expectedSha.toLowerCase();
19496
- const verified = Boolean(expected) && actualSha.toLowerCase() === expected;
19497
- if (!verified && expected && (options.verifyHash ?? true)) {
19498
- throw new StorageError(
19499
- `hash mismatch: expected=${expected} actual=${actualSha.toLowerCase()}`,
19500
- "EHASH",
19501
- path
19502
- );
19643
+ async getGroupIndex(params2) {
19644
+ const merged = stripNil2(params2);
19645
+ const groupId = String(merged.group_id ?? "").trim();
19646
+ if (!groupId) throw new Error("group_id is required");
19647
+ const result = await this.getSettings({ group_id: groupId, keys: [GROUP_INDEX_KEY] });
19648
+ const resultMap = result;
19649
+ const groupAid = String(resultMap.group_aid ?? "").trim();
19650
+ if (!groupAid) throw new Error("group.get_settings response group_aid is required");
19651
+ let groupIndex = null;
19652
+ for (const item of result.settings ?? []) {
19653
+ if (item?.key !== GROUP_INDEX_KEY) continue;
19654
+ groupIndex = item.value;
19655
+ break;
19503
19656
  }
19657
+ if (!groupIndex) {
19658
+ return { group_id: resultMap.group_id, group_aid: groupAid, group_index: null, meta: {}, entries: [], settings: {} };
19659
+ }
19660
+ const parsed = parseGroupIndex(groupIndex);
19661
+ this.validateGroupIndexEntries(parsed.entries);
19662
+ await this.verifyPulledGroupIndex(groupId, groupAid, groupIndex, parsed);
19663
+ const etag = String(parsed.meta.etag ?? "");
19664
+ const settings = await this.hydrateGroupIndexSettings(groupId, groupAid, parsed.entries, etag, groupIndex);
19504
19665
  return {
19505
- path,
19506
- size: data.length,
19507
- sha256: expectedSha || actualSha,
19508
- verified,
19509
- data
19666
+ group_id: resultMap.group_id,
19667
+ group_aid: groupAid,
19668
+ group_index: groupIndex,
19669
+ meta: parsed.meta,
19670
+ entries: parsed.entries,
19671
+ settings
19510
19672
  };
19511
19673
  }
19512
- async list(path, options = {}) {
19513
- if (options.recursive) {
19514
- const result = [];
19515
- const pending = [path];
19516
- while (pending.length) {
19517
- const current = pending.shift();
19518
- const children = await this.list(current, { ...options, recursive: false });
19519
- result.push(...children);
19520
- pending.push(...children.filter((node) => node.type === "dir").map((node) => node.path));
19674
+ async verifyPulledGroupIndex(groupId, groupAid, groupIndex, parsed) {
19675
+ if (String(parsed.meta?.group_aid ?? "").trim() !== groupAid) throw new Error("group.index group_aid does not match group.get_settings response");
19676
+ const signedBy = String(parsed.meta?.signed_by ?? "").trim();
19677
+ if (!signedBy) throw new Error("group.index signed_by is required");
19678
+ const certFingerprint = String(parsed.meta?.cert_fingerprint ?? "").trim();
19679
+ const clientAny = this.client;
19680
+ const currentAid = clientAny.currentAid;
19681
+ let signer = currentAid?.aid === signedBy && !certFingerprint ? currentAid : null;
19682
+ if (currentAid?.aid === signedBy && certFingerprint && typeof currentAid.certPem === "string" && currentAid.certPem) {
19683
+ try {
19684
+ if (await certMatchesFingerprint(currentAid.certPem, certFingerprint)) signer = currentAid;
19685
+ } catch {
19686
+ signer = null;
19687
+ }
19688
+ }
19689
+ if (!signer && typeof clientAny.lookupPeer === "function") {
19690
+ signer = await clientAny.lookupPeer(signedBy, certFingerprint || void 0);
19691
+ }
19692
+ if (!signer) throw new Error(`group.index signer is unavailable: ${signedBy}`);
19693
+ const verified = await verifyGroupIndex(groupIndex, signer);
19694
+ if (!verified.ok) throw new Error(verified.error.message || "group.index verification failed");
19695
+ if (!verified.data.valid) throw new Error(`group.index verification failed: ${verified.data.reason || "invalid signature"}`);
19696
+ const adminsResult = await this.call("group.get_admins", { group_id: groupId });
19697
+ const adminsMap = adminsResult;
19698
+ if (String(adminsMap.group_aid ?? "").trim() !== groupAid) throw new Error("group.get_admins response group_aid does not match group.get_settings response");
19699
+ const authorized = Array.isArray(adminsMap.admins) && adminsMap.admins.some(
19700
+ (item) => String(item?.aid ?? "").trim() === signedBy && ["owner", "admin"].includes(String(item?.role ?? "").trim().toLowerCase())
19701
+ );
19702
+ if (!authorized) throw new Error(`group.index signer is not a current owner or admin: ${signedBy}`);
19703
+ }
19704
+ async updateGroupIndex(params2) {
19705
+ const merged = stripNil2(params2);
19706
+ const groupId = String(merged.group_id ?? "").trim();
19707
+ const settings = merged.settings;
19708
+ if (!groupId) throw new Error("group_id is required");
19709
+ if (!settings || typeof settings !== "object" || Array.isArray(settings) || Object.keys(settings).length === 0) {
19710
+ throw new Error("settings must be a non-empty object");
19711
+ }
19712
+ const signer = merged.signer ?? this.client.currentAid;
19713
+ if (!signer) throw new Error("signer is required");
19714
+ const lastModified = Math.trunc(Number(merged.last_modified ?? Date.now()));
19715
+ const maxAttempts = Math.max(1, Math.trunc(Number(merged.max_attempts ?? 2)));
19716
+ let lastError = null;
19717
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
19718
+ const current = await this.getGroupIndex({ group_id: groupId });
19719
+ const groupAid = String(current.group_aid ?? "").trim();
19720
+ const currentIndex = current.group_index ?? null;
19721
+ const expectedEtag = currentIndex ? String(parseGroupIndex(currentIndex).meta.etag ?? "") : "";
19722
+ const baseSettings = current.settings && typeof current.settings === "object" && !Array.isArray(current.settings) ? current.settings : {};
19723
+ const signedSettings = await prepareGroupSettingsWithIndex({
19724
+ groupAid,
19725
+ settings,
19726
+ signer,
19727
+ lastModified,
19728
+ baseIndex: currentIndex
19729
+ });
19730
+ try {
19731
+ const result = await this.setSettings({
19732
+ group_id: groupId,
19733
+ settings: signedSettings,
19734
+ expected_index_etag: expectedEtag
19735
+ });
19736
+ const pushedEtag = String(parseGroupIndex(signedSettings[GROUP_INDEX_KEY]).meta.etag ?? "");
19737
+ const clientAny = this.client;
19738
+ if (typeof clientAny.cacheGroupIndexSettings === "function") {
19739
+ const parsed = parseGroupIndex(signedSettings[GROUP_INDEX_KEY]);
19740
+ await clientAny.cacheGroupIndexSettings(groupAid, { ...baseSettings, ...settings }, {
19741
+ entries: parsed.entries,
19742
+ etag: pushedEtag,
19743
+ groupIndex: signedSettings[GROUP_INDEX_KEY]
19744
+ });
19745
+ }
19746
+ return result;
19747
+ } catch (exc) {
19748
+ if (!String(exc?.message ?? exc).includes("etag conflict")) throw exc;
19749
+ lastError = exc;
19750
+ }
19751
+ }
19752
+ if (lastError) throw lastError;
19753
+ throw new Error("updateGroupIndex failed");
19754
+ }
19755
+ async getIndexedSettings(groupId, keys) {
19756
+ const clientAny = this.client;
19757
+ if (typeof clientAny.getGroupIndexCachedSettings === "function") {
19758
+ const cached = await clientAny.getGroupIndexCachedSettings(groupId, keys);
19759
+ if (cached && typeof cached === "object") return { groupId, settings: cached };
19760
+ }
19761
+ const result = await this.getGroupIndex({ group_id: groupId });
19762
+ const resultMap = result;
19763
+ const allSettings = resultMap.settings && typeof resultMap.settings === "object" && !Array.isArray(resultMap.settings) ? resultMap.settings : {};
19764
+ const settings = Object.fromEntries(keys.filter((key) => key in allSettings).map((key) => [key, allSettings[key]]));
19765
+ const groupAid = String(resultMap.group_aid ?? "").trim();
19766
+ if (groupAid && groupAid !== groupId && typeof clientAny.cacheGroupIndexSettings === "function" && resultMap.group_index) {
19767
+ await clientAny.cacheGroupIndexSettings(groupId, allSettings, { entries: resultMap.entries, etag: String(resultMap.meta?.etag ?? ""), groupIndex: resultMap.group_index });
19768
+ }
19769
+ return { groupId: String(resultMap.group_id ?? groupId), settings };
19770
+ }
19771
+ async hydrateGroupIndexSettings(groupId, groupAid, entries, etag, groupIndex) {
19772
+ const keys = entries.map((item) => String(item.key));
19773
+ const clientAny = this.client;
19774
+ const settings = {};
19775
+ if (keys.length > 0) {
19776
+ const fetchedResult = await this.getSettings({ group_id: groupId, keys });
19777
+ const fetchedMap = fetchedResult;
19778
+ if (String(fetchedMap.group_aid ?? "").trim() !== groupAid) throw new Error("group settings response group_aid does not match verified group.index");
19779
+ const seen = /* @__PURE__ */ new Set();
19780
+ for (const item of fetchedMap.settings ?? []) {
19781
+ const key = String(item?.key ?? "").trim();
19782
+ if (!key || seen.has(key)) throw new Error(`group settings response contains duplicate or empty key: ${key}`);
19783
+ seen.add(key);
19784
+ settings[key] = item?.value;
19785
+ }
19786
+ for (const entry of entries) {
19787
+ const key = String(entry.key);
19788
+ if (!seen.has(key)) throw new Error(`group.index setting is missing: ${key}`);
19789
+ if (await groupIndexSettingEtag(settings[key]) !== String(entry.etag)) throw new Error(`group.index setting etag mismatch: ${key}`);
19521
19790
  }
19522
- return result;
19523
19791
  }
19524
- const owner = this.owner(options.owner);
19525
- const bucket = options.bucket ?? "default";
19526
- const key = pathToKey(path);
19527
- const raw = await this.lowlevel.fsList({
19528
- owner,
19529
- bucket,
19530
- path: key,
19531
- page: options.page,
19532
- size: options.size,
19533
- marker: options.marker,
19534
- token: options.token
19535
- });
19536
- const items = raw.nodes ?? raw.items ?? [];
19537
- return items.map((item) => nodeFromAny(item));
19538
- }
19539
- async stat(path, options = {}) {
19540
- const owner = this.owner(options.owner);
19541
- const raw = await this.lowlevel.fsStat({ owner, bucket: options.bucket ?? "default", path: pathToKey(path), token: options.token });
19542
- return nodeFromAny(raw);
19792
+ if (typeof clientAny.cacheGroupIndexSettings === "function") {
19793
+ await clientAny.cacheGroupIndexSettings(groupAid, settings, { entries, etag, groupIndex });
19794
+ }
19795
+ return settings;
19543
19796
  }
19544
- async lstat(path, options = {}) {
19545
- const owner = this.owner(options.owner);
19546
- const raw = await this.lowlevel.fsLstat({ owner, bucket: options.bucket ?? "default", path: pathToKey(path), token: options.token });
19547
- return nodeFromAny(raw);
19797
+ validateGroupIndexEntries(entries) {
19798
+ const seen = /* @__PURE__ */ new Set();
19799
+ for (const entry of entries) {
19800
+ const key = String(entry?.key ?? "").trim();
19801
+ const source = String(entry?.source ?? "").trim();
19802
+ const etag = String(entry?.etag ?? "").trim();
19803
+ if (!key) throw new Error("group.index entry key is required");
19804
+ if (seen.has(key)) throw new Error(`group.index contains duplicate key: ${key}`);
19805
+ if (source !== "db") throw new Error(`group.index entry source is unsupported: ${source || "<empty>"}`);
19806
+ if (!etag) throw new Error(`group.index entry etag is required: ${key}`);
19807
+ seen.add(key);
19808
+ }
19548
19809
  }
19549
- async mkdir(path, options = {}) {
19550
- const raw = await this.lowlevel.fsMkdir({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), parents: options.parents });
19551
- return nodeFromAny(raw.node ?? raw);
19810
+ send(params2) {
19811
+ validateGroupIDFormat(params2?.group_id, "group_id");
19812
+ return this.call("group.send", params2);
19552
19813
  }
19553
- async touch(path, options = {}) {
19554
- const raw = await this.lowlevel.fsTouch({
19555
- owner: this.owner(options.owner),
19556
- bucket: options.bucket ?? "default",
19557
- path: pathToKey(path),
19558
- parents: options.parents,
19559
- noCreate: options.noCreate,
19560
- mtime: options.mtime,
19561
- followSymlinks: options.followSymlinks
19562
- });
19563
- return nodeFromAny(raw.node ?? raw);
19814
+ recall(params2) {
19815
+ return this.call("group.recall", params2);
19564
19816
  }
19565
- async remove(path, options = {}) {
19566
- const raw = await this.lowlevel.fsRemove({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), recursive: options.recursive });
19567
- return { path, removedCount: Number(raw.removed_count ?? raw.deleted_count ?? 0) };
19817
+ pull(params2) {
19818
+ validateGroupIDFormat(params2?.group_id, "group_id");
19819
+ return this.call("group.pull", params2);
19568
19820
  }
19569
- async rename(src, dst, options = {}) {
19570
- const raw = await this.lowlevel.fsRename({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", src: pathToKey(src), dst: pathToKey(dst), overwrite: options.overwrite, expectedVersion: options.expectedVersion });
19571
- return nodeFromAny(raw.node ?? raw);
19821
+ history(params2) {
19822
+ return this.call("group.history", params2);
19572
19823
  }
19573
- async copy(src, dst, options = {}) {
19574
- const raw = await this.lowlevel.fsCopy({
19575
- owner: this.owner(options.owner),
19576
- bucket: options.bucket ?? "default",
19577
- src: pathToKey(src),
19578
- dst: pathToKey(dst),
19579
- overwrite: options.overwrite,
19580
- followSymlinks: options.followSymlinks,
19581
- recursive: options.recursive,
19582
- dstOwner: options.dstOwner,
19583
- dstBucket: options.dstBucket
19584
- });
19585
- return nodeFromAny(raw.node ?? raw);
19824
+ pullEvents(params2) {
19825
+ return this.call("group.pull_events", params2);
19586
19826
  }
19587
- async find(path, options = {}) {
19588
- const raw = await this.lowlevel.fsFind({
19589
- owner: this.owner(options.owner),
19590
- bucket: options.bucket ?? "default",
19591
- path: pathToKey(path),
19592
- name: options.name,
19593
- nodeType: options.nodeType,
19594
- size: options.size,
19595
- mtime: options.mtime,
19596
- page: options.page,
19597
- pageSize: options.pageSize,
19598
- token: options.token
19599
- });
19600
- const items = raw.nodes ?? raw.items ?? [];
19601
- return items.map((item) => nodeFromAny(item));
19827
+ ackMessages(params2) {
19828
+ return this.call("group.ack_messages", params2);
19602
19829
  }
19603
- async df(options = {}) {
19604
- const owner = this.owner(options.owner);
19605
- const raw = await this.lowlevel.fsDf({ owner, bucket: options.bucket ?? "default" });
19606
- return usageFromAny(raw, owner ?? void 0);
19830
+ ackEvents(params2) {
19831
+ return this.call("group.ack_events", params2);
19607
19832
  }
19608
- async du(path, options = {}) {
19609
- const nodes = await this.find(path, {
19610
- owner: options.owner,
19611
- bucket: options.bucket,
19612
- page: 1,
19613
- pageSize: options.pageSize ?? 1e3,
19614
- token: options.token
19615
- });
19616
- const rootParts = pathToKey(path).split("/").filter(Boolean);
19617
- let sizeBytes = 0;
19618
- let fileCount = 0;
19619
- let dirCount = 0;
19620
- let symlinkCount = 0;
19621
- let truncated = false;
19622
- for (const node of nodes) {
19623
- const nodeParts = pathToKey(node.path).split("/").filter(Boolean);
19624
- const depth = Math.max(0, nodeParts.length - rootParts.length);
19625
- if (options.maxDepth !== void 0 && depth > options.maxDepth) {
19626
- truncated = true;
19627
- continue;
19628
- }
19629
- if (node.type === "file") {
19630
- fileCount += 1;
19631
- sizeBytes += node.size;
19632
- } else if (node.type === "dir") {
19633
- dirCount += 1;
19634
- } else if (node.type === "symlink") {
19635
- symlinkCount += 1;
19636
- }
19637
- }
19833
+ documentSettingResult(groupId, keyName, settings) {
19834
+ const contentKey = `${keyName}.content`;
19835
+ const attachmentsKey = `${keyName}.attachments`;
19638
19836
  return {
19639
- path,
19640
- sizeBytes,
19641
- fileCount,
19642
- dirCount,
19643
- symlinkCount,
19644
- maxDepth: options.maxDepth,
19645
- truncated
19837
+ group_id: groupId,
19838
+ setting: {
19839
+ group_id: groupId,
19840
+ key_name: keyName,
19841
+ content: settings[contentKey] ?? "",
19842
+ attachments: settings[attachmentsKey] ?? [],
19843
+ updated_by: settings[`${contentKey}.updated_by`] ?? "",
19844
+ updated_at: settings[`${contentKey}.updated_at`] ?? 0
19845
+ }
19646
19846
  };
19647
19847
  }
19648
- async mount(source, mountPath, options = {}) {
19649
- const owner = this.owner(options.owner);
19650
- const mountRef = parsePathLike(mountPath, owner);
19651
- const sourceRef = parsePathLike(source, options.sourceOwner ?? this.defaultOwner);
19652
- const raw = await this.lowlevel.fsMount({
19653
- owner: mountRef.owner,
19654
- bucket: options.bucket ?? "default",
19655
- mountPath: pathToKey(mountRef.path),
19656
- sourceAid: String(sourceRef.owner ?? ""),
19657
- sourceBucket: options.sourceBucket,
19658
- sourcePath: pathToKey(sourceRef.path),
19659
- readonly: options.readonly,
19660
- expiresAt: options.expiresAt ?? options.expires,
19661
- requireApproval: options.requireApproval
19662
- });
19663
- return nodeFromAny(raw.node ?? raw.mount ?? raw);
19664
- }
19665
- async mountVolume(volumeId, mountPath, options = {}) {
19666
- const owner = this.owner(options.owner);
19667
- const mountRef = parsePathLike(mountPath, owner);
19668
- const raw = await this.lowlevel.fsMount({
19669
- owner: mountRef.owner,
19670
- bucket: options.bucket ?? "default",
19671
- mountPath: pathToKey(mountRef.path),
19672
- readonly: options.readonly,
19673
- expiresAt: options.expiresAt ?? options.expires,
19674
- requireApproval: options.requireApproval,
19675
- volumeId
19676
- });
19677
- return nodeFromAny(raw.node ?? raw.mount ?? raw);
19678
- }
19679
- approveMount(mountPath, options = {}) {
19680
- const owner = this.owner(options.owner);
19681
- const mountRef = parsePathLike(mountPath, owner);
19682
- return this.lowlevel.fsApprove({
19683
- owner: mountRef.owner,
19684
- bucket: options.bucket ?? "default",
19685
- mountPath: pathToKey(mountRef.path),
19686
- mountId: options.mountId,
19687
- requestId: options.requestId
19688
- });
19689
- }
19690
- rejectMount(mountPath, options = {}) {
19691
- const owner = this.owner(options.owner);
19692
- const mountRef = parsePathLike(mountPath, owner);
19693
- return this.lowlevel.fsReject({
19694
- owner: mountRef.owner,
19695
- bucket: options.bucket ?? "default",
19696
- mountPath: pathToKey(mountRef.path),
19697
- mountId: options.mountId,
19698
- requestId: options.requestId
19699
- });
19700
- }
19701
- async unmount(mountPath, options = {}) {
19702
- const owner = this.owner(options.owner);
19703
- const mountRef = parsePathLike(mountPath, owner);
19704
- const raw = await this.lowlevel.fsUnmount({
19705
- owner: mountRef.owner,
19706
- bucket: options.bucket ?? "default",
19707
- mountPath: pathToKey(mountRef.path)
19708
- });
19709
- return unmountFromAny(raw, {
19710
- owner: mountRef.owner,
19711
- bucket: options.bucket ?? "default",
19712
- mountPath: pathToKey(mountRef.path)
19713
- });
19714
- }
19715
- async symlink(target, linkPath, options = {}) {
19716
- const raw = await this.lowlevel.createSymlink({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(linkPath), target, overwrite: options.overwrite });
19717
- return nodeFromAny(raw.symlink ?? raw);
19848
+ async getSettingWithIndex(params2) {
19849
+ const merged = params2 || {};
19850
+ const groupId = merged.group_id;
19851
+ if (!groupId) throw new Error("group_id is required");
19852
+ const keyName = indexedDocumentKeyName(merged);
19853
+ const { groupId: resultGroupId, settings } = await this.getIndexedSettings(
19854
+ String(groupId),
19855
+ [`${keyName}.content`, `${keyName}.attachments`]
19856
+ );
19857
+ return this.documentSettingResult(resultGroupId, keyName, settings);
19718
19858
  }
19719
- async readlink(path, options = {}) {
19720
- const raw = await this.lowlevel.readlink({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path) });
19721
- return nodeFromAny(raw.symlink ?? raw);
19859
+ async updateSettingWithIndex(params2) {
19860
+ const merged = params2 || {};
19861
+ const groupId = merged.group_id;
19862
+ if (!groupId) throw new Error("group_id is required");
19863
+ const keyName = indexedDocumentKeyName(merged);
19864
+ if (!("content" in merged)) throw new Error("content is required");
19865
+ const content = merged.content;
19866
+ const attachments = merged.attachments ?? [];
19867
+ const settingsUpdate = { [`${keyName}.content`]: content };
19868
+ if ("attachments" in merged) {
19869
+ settingsUpdate[`${keyName}.attachments`] = attachments;
19870
+ }
19871
+ const result = await this.updateGroupIndex(indexUpdateParams(groupId, settingsUpdate, merged));
19872
+ const resultGroupId = String(result.group_id ?? groupId);
19873
+ return {
19874
+ group_id: resultGroupId,
19875
+ setting: {
19876
+ group_id: resultGroupId,
19877
+ key_name: keyName,
19878
+ content,
19879
+ attachments,
19880
+ updated_by: "",
19881
+ updated_at: 0
19882
+ }
19883
+ };
19722
19884
  }
19723
- async repoint(path, newTarget, options = {}) {
19724
- const raw = await this.lowlevel.atomicRepoint({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), newTarget, expectedVersion: options.expectedVersion });
19725
- if (raw.ok === false) throw new StorageConflictError("symlink version conflict", "ECONFLICT", path, raw);
19726
- return nodeFromAny(raw.symlink ?? raw);
19885
+ async getAnnouncement(params2) {
19886
+ const merged = params2 || {};
19887
+ const result = await this.getSettingWithIndex({ ...merged, keyName: "announcement" });
19888
+ const resultMap = result;
19889
+ const setting = resultMap.setting;
19890
+ return {
19891
+ group_id: resultMap.group_id,
19892
+ announcement: {
19893
+ group_id: setting.group_id,
19894
+ content: setting.content,
19895
+ attachments: setting.attachments,
19896
+ updated_by: setting.updated_by,
19897
+ updated_at: setting.updated_at
19898
+ }
19899
+ };
19727
19900
  }
19728
- async renameSymlink(src, dst, options = {}) {
19729
- const raw = await this.lowlevel.renameSymlink({
19730
- owner: this.owner(options.owner),
19731
- bucket: options.bucket ?? "default",
19732
- path: pathToKey(src),
19733
- newPath: pathToKey(dst),
19734
- overwrite: options.overwrite,
19735
- expectedVersion: options.expectedVersion
19736
- });
19737
- if (raw.ok === false) throw new StorageConflictError("symlink version conflict", "ECONFLICT", src, raw);
19738
- return nodeFromAny(raw.symlink ?? raw);
19901
+ async updateAnnouncement(params2) {
19902
+ const merged = params2 || {};
19903
+ const result = await this.updateSettingWithIndex({ ...merged, keyName: "announcement" });
19904
+ const resultMap = result;
19905
+ const setting = resultMap.setting;
19906
+ return {
19907
+ group_id: resultMap.group_id,
19908
+ announcement: {
19909
+ group_id: resultMap.group_id,
19910
+ content: setting.content,
19911
+ attachments: setting.attachments
19912
+ }
19913
+ };
19739
19914
  }
19740
- setAcl(path, options) {
19741
- return this.lowlevel.setAcl({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), granteeAid: options.granteeAid, perms: options.perms, expiresAt: options.expiresAt, maxUses: options.maxUses });
19915
+ async getRules(params2) {
19916
+ const merged = params2 || {};
19917
+ const result = await this.getSettingWithIndex({ ...merged, keyName: "rules" });
19918
+ const resultMap = result;
19919
+ const setting = resultMap.setting;
19920
+ return {
19921
+ group_id: resultMap.group_id,
19922
+ rules: {
19923
+ group_id: setting.group_id,
19924
+ content: setting.content,
19925
+ attachments: setting.attachments,
19926
+ updated_by: setting.updated_by,
19927
+ updated_at: setting.updated_at
19928
+ }
19929
+ };
19742
19930
  }
19743
- removeAcl(path, options) {
19744
- return this.lowlevel.removeAcl({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), granteeAid: options.granteeAid });
19931
+ async updateRules(params2) {
19932
+ const merged = params2 || {};
19933
+ const result = await this.updateSettingWithIndex({ ...merged, keyName: "rules" });
19934
+ const resultMap = result;
19935
+ const setting = resultMap.setting;
19936
+ return {
19937
+ group_id: resultMap.group_id,
19938
+ rules: {
19939
+ group_id: resultMap.group_id,
19940
+ content: setting.content,
19941
+ attachments: setting.attachments
19942
+ }
19943
+ };
19745
19944
  }
19746
- listAcl(path, options = {}) {
19747
- return this.lowlevel.listAcl({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path) });
19945
+ async getJoinRequirements(params2) {
19946
+ const merged = params2 || {};
19947
+ const groupId = merged.group_id;
19948
+ if (!groupId) throw new Error("group_id is required");
19949
+ const { groupId: resultGroupId, settings } = await this.getIndexedSettings(
19950
+ String(groupId),
19951
+ ["join.mode", "join.question", "join.auto_approve_patterns", "join.max_pending", "join.attachments"]
19952
+ );
19953
+ return {
19954
+ group_id: resultGroupId,
19955
+ join_requirements: {
19956
+ group_id: resultGroupId,
19957
+ mode: settings["join.mode"] || "open",
19958
+ question: settings["join.question"] || "",
19959
+ auto_approve_patterns: settings["join.auto_approve_patterns"] || [],
19960
+ max_pending: settings["join.max_pending"] || 100,
19961
+ attachments: settings["join.attachments"] || [],
19962
+ updated_by: settings["join.mode.updated_by"] || "",
19963
+ updated_at: settings["join.mode.updated_at"] || 0
19964
+ }
19965
+ };
19748
19966
  }
19749
- async setVisibility(path, options) {
19750
- const raw = await this.lowlevel.setVisibility({
19751
- owner: this.owner(options.owner),
19752
- bucket: options.bucket ?? "default",
19753
- path: pathToKey(path),
19754
- visibility: options.visibility,
19755
- allowRoles: options.allowRoles
19756
- });
19757
- return nodeFromAny(raw.node ?? raw);
19967
+ async updateJoinRequirements(params2) {
19968
+ const merged = params2 || {};
19969
+ const groupId = merged.group_id;
19970
+ if (!groupId) throw new Error("group_id is required");
19971
+ const settingsUpdate = {};
19972
+ if ("mode" in merged) settingsUpdate["join.mode"] = merged.mode;
19973
+ if ("question" in merged) settingsUpdate["join.question"] = merged.question;
19974
+ if ("auto_approve_patterns" in merged) settingsUpdate["join.auto_approve_patterns"] = merged.auto_approve_patterns;
19975
+ if ("max_pending" in merged) settingsUpdate["join.max_pending"] = merged.max_pending;
19976
+ if ("attachments" in merged) settingsUpdate["join.attachments"] = merged.attachments;
19977
+ if (Object.keys(settingsUpdate).length === 0) {
19978
+ throw new Error("at least one field to update is required");
19979
+ }
19980
+ const result = await this.updateGroupIndex(indexUpdateParams(groupId, settingsUpdate, merged));
19981
+ return {
19982
+ group_id: result.group_id,
19983
+ join_requirements: {
19984
+ group_id: result.group_id,
19985
+ mode: merged.mode,
19986
+ question: merged.question,
19987
+ auto_approve_patterns: merged.auto_approve_patterns,
19988
+ max_pending: merged.max_pending,
19989
+ attachments: merged.attachments || []
19990
+ }
19991
+ };
19758
19992
  }
19759
- checkAccess(path, options = {}) {
19760
- return this.lowlevel.checkAccess({
19761
- owner: this.owner(options.owner),
19762
- bucket: options.bucket ?? "default",
19763
- path: pathToKey(path),
19764
- operation: options.operation ?? "read",
19765
- token: options.token,
19766
- followSymlinks: options.followSymlinks ?? true
19767
- });
19993
+ getOnlineMembers(params2) {
19994
+ return this.call("group.get_online_members", params2);
19768
19995
  }
19769
- issueToken(path, options = {}) {
19770
- return this.lowlevel.issueToken({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), expiresAt: options.expiresAt, maxReads: options.maxReads });
19996
+ };
19997
+ var StreamFacade = class extends RpcFacade {
19998
+ create(params2) {
19999
+ return this.call("stream.create", params2);
19771
20000
  }
19772
- revokeToken(path, options) {
19773
- return this.lowlevel.revokeToken({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), token: options.token });
20001
+ close(params2) {
20002
+ return this.call("stream.close", params2);
19774
20003
  }
19775
- listTokens(path, options = {}) {
19776
- return this.lowlevel.listTokens({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path) });
20004
+ getInfo(params2) {
20005
+ return this.call("stream.get_info", params2);
19777
20006
  }
19778
- async getUsage(options = {}) {
19779
- const owner = this.owner(options.owner);
19780
- const raw = await this.lowlevel.getQuota({ owner, bucket: options.bucket ?? "default" });
19781
- return usageFromAny(raw, owner ?? "");
20007
+ listActive(params2) {
20008
+ return this.call("stream.list_active", params2);
19782
20009
  }
19783
20010
  };
19784
20011
 
@@ -24945,6 +25172,12 @@ var V2E2EECoordinator = class {
24945
25172
  await client._publishAppEvent(eventName, safeEvent, "pending_retry");
24946
25173
  continue;
24947
25174
  }
25175
+ if (!entry.groupId && String(entry.msg.storage ?? "") === "volatile") {
25176
+ await this.publishPendingV2VolatileMessage(entry.msg, plaintext, "pending_retry");
25177
+ client._v2SenderIKPending.delete(key);
25178
+ client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
25179
+ continue;
25180
+ }
24948
25181
  client._v2SenderIKPending.delete(key);
24949
25182
  const seq2 = Number(entry.msg.seq ?? 0);
24950
25183
  if (entry.groupId) {
@@ -24961,6 +25194,27 @@ var V2E2EECoordinator = class {
24961
25194
  client._v2SenderIKFetching.delete(fetchKey);
24962
25195
  }
24963
25196
  }
25197
+ async publishPendingV2VolatileMessage(rawMessage, plaintext, source) {
25198
+ const delivery = this.client._delivery;
25199
+ const claim = delivery.beginVolatileDelivery(rawMessage);
25200
+ if (claim === "inflight") return;
25201
+ if (claim === "seen") {
25202
+ await delivery.ackVolatileMessages([rawMessage], true, false);
25203
+ return;
25204
+ }
25205
+ try {
25206
+ const publicMessage = delivery.publicVolatileMessage(plaintext);
25207
+ for (const key of ["storage", "volatile_cursor", "created_at", "expires_at", "persist"]) {
25208
+ if (key in rawMessage) publicMessage[key] = rawMessage[key];
25209
+ }
25210
+ const appEvent = delivery.p2pAppEventForMessage(publicMessage);
25211
+ await this.client._publishAppEvent(appEvent.event, appEvent.payload, source);
25212
+ delivery.markVolatileSeen(rawMessage);
25213
+ } finally {
25214
+ delivery.finishVolatileDelivery(rawMessage);
25215
+ }
25216
+ await delivery.ackVolatileMessages([rawMessage], true, false);
25217
+ }
24964
25218
  async sendV2(to, payload, opts) {
24965
25219
  const client = this.client;
24966
25220
  await client._ensureV2SessionReady(
@@ -24968,6 +25222,7 @@ var V2E2EECoordinator = class {
24968
25222
  "V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)"
24969
25223
  );
24970
25224
  const toAid = String(to ?? "").trim();
25225
+ const targetDeviceId = String(opts?.deviceId ?? "").trim();
24971
25226
  if (!toAid) throw new ValidationError("message.send requires 'to'");
24972
25227
  if (!isJsonObject(payload)) throw new ValidationError("message.send payload must be a dict for V2 encryption");
24973
25228
  const attempt = async (useCache) => {
@@ -24976,6 +25231,7 @@ var V2E2EECoordinator = class {
24976
25231
  payload,
24977
25232
  messageId: opts?.messageId,
24978
25233
  timestamp: opts?.timestamp,
25234
+ deviceId: targetDeviceId || void 0,
24979
25235
  protectedHeaders: opts?.protectedHeaders,
24980
25236
  context: opts?.context,
24981
25237
  useCache
@@ -24984,6 +25240,8 @@ var V2E2EECoordinator = class {
24984
25240
  to: toAid,
24985
25241
  payload: envelope,
24986
25242
  message_id: opts?.messageId,
25243
+ ...opts?.ttl !== void 0 ? { ttl: opts.ttl } : {},
25244
+ ...targetDeviceId ? { device_id: targetDeviceId } : {},
24987
25245
  encrypt: false,
24988
25246
  _skip_send_result_envelope: true
24989
25247
  });
@@ -25242,6 +25500,7 @@ var V2E2EECoordinator = class {
25242
25500
  const client = this.client;
25243
25501
  await client._ensureV2SessionReady("message.pull");
25244
25502
  const ns = client._aid ? `p2p:${client._aid}` : "";
25503
+ let volatileCursor = String(opts?.afterVolatileCursor ?? client._delivery.volatilePullCursor() ?? "").trim();
25245
25504
  if (ns) client._delivery.onPullStarted?.(ns);
25246
25505
  if (opts?.windowMode === "tail") {
25247
25506
  if (!opts.gateLocked) {
@@ -25261,6 +25520,7 @@ var V2E2EECoordinator = class {
25261
25520
  if (ns && !opts?.gateLocked) {
25262
25521
  const key = pullGateKeyForClient(client, "message.v2.pull", {
25263
25522
  after_seq: afterSeq,
25523
+ after_volatile_cursor: volatileCursor,
25264
25524
  force: opts?.force === true,
25265
25525
  limit
25266
25526
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
@@ -25273,6 +25533,7 @@ var V2E2EECoordinator = class {
25273
25533
  let nextAfterSeq = opts?.force ? afterSeq : afterSeq || (ns ? client._seqTracker.getContiguousSeq(ns) : 0);
25274
25534
  let pullGateKey = ns ? pullGateKeyForClient(client, "message.v2.pull", {
25275
25535
  after_seq: afterSeq,
25536
+ after_volatile_cursor: volatileCursor,
25276
25537
  force: opts?.force === true,
25277
25538
  limit
25278
25539
  }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`) : "";
@@ -25284,14 +25545,26 @@ var V2E2EECoordinator = class {
25284
25545
  const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
25285
25546
  while (pageCount < maxPages) {
25286
25547
  pageCount += 1;
25548
+ const volatileCursorBeforePage = volatileCursor;
25287
25549
  const ackUpToSeq = pendingAckSeq;
25288
25550
  const pendingInlineGeneration = ackUpToSeq > 0 ? client._delivery?.pendingP2PInlineAckGeneration?.(ns) ?? null : null;
25289
- const result = await client._callRawV2Rpc("message.v2.pull", {
25290
- after_seq: nextAfterSeq,
25291
- limit,
25292
- ...opts?.force ? { force: true } : {},
25293
- ...ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}
25294
- }, pullGateKey);
25551
+ let result;
25552
+ try {
25553
+ result = await client._callRawV2Rpc("message.v2.pull", {
25554
+ after_seq: nextAfterSeq,
25555
+ limit,
25556
+ ...volatileCursor ? { after_volatile_cursor: volatileCursor } : {},
25557
+ ...opts?.force ? { force: true } : {},
25558
+ ...ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}
25559
+ }, pullGateKey);
25560
+ } catch (exc) {
25561
+ const text3 = exc instanceof Error ? exc.message : String(exc);
25562
+ if (!volatileCursor || !text3.toLowerCase().includes("volatile cursor epoch mismatch")) throw exc;
25563
+ client._delivery.resetVolatilePullCursor();
25564
+ volatileCursor = "";
25565
+ pageCount -= 1;
25566
+ continue;
25567
+ }
25295
25568
  if (ackUpToSeq > 0) {
25296
25569
  const actualAckSeq = client._delivery?.resolveP2PPullAckSeq?.(result, ackUpToSeq) ?? 0;
25297
25570
  if (actualAckSeq >= ackUpToSeq) {
@@ -25309,6 +25582,12 @@ var V2E2EECoordinator = class {
25309
25582
  }
25310
25583
  }
25311
25584
  const pageMessages = Array.isArray(result?.messages) ? result.messages : [];
25585
+ const volatileMessages = client._delivery.volatileMessages(pageMessages);
25586
+ if (volatileMessages.length > 0) {
25587
+ decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, "pull", true));
25588
+ volatileCursor = client._delivery.volatilePullCursor();
25589
+ }
25590
+ const volatileAdvanced = Boolean(volatileCursor) && volatileCursor !== volatileCursorBeforePage;
25312
25591
  const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
25313
25592
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
25314
25593
  const hasMoreField = Object.prototype.hasOwnProperty.call(result, "has_more");
@@ -25485,7 +25764,7 @@ var V2E2EECoordinator = class {
25485
25764
  const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
25486
25765
  const rawCount = messages.length;
25487
25766
  if (blockedSeq > 0) break;
25488
- const shouldContinue = shouldContinueForwardPage({
25767
+ let shouldContinue = shouldContinueForwardPage({
25489
25768
  rawCount,
25490
25769
  nextAfterSeq,
25491
25770
  nextAfter,
@@ -25496,10 +25775,16 @@ var V2E2EECoordinator = class {
25496
25775
  pendingAckSeq,
25497
25776
  observedUpper: typeof client._seqTracker.getMaxSeenSeq === "function" ? client._seqTracker.getMaxSeenSeq(ns) : nextAfter
25498
25777
  });
25778
+ const volatileHasMoreField = Object.prototype.hasOwnProperty.call(result, "volatile_has_more");
25779
+ const volatileHasMore = result.volatile_has_more === true;
25780
+ if (!shouldContinue && volatileAdvanced && (volatileHasMore || hasMore || !volatileHasMoreField && limit > 0 && pageMessages.length >= limit)) {
25781
+ shouldContinue = true;
25782
+ }
25499
25783
  if (!shouldContinue) break;
25500
25784
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
25501
25785
  const nextKey = pullGateKeyForClient(client, "message.v2.pull", {
25502
25786
  after_seq: nextAfter,
25787
+ after_volatile_cursor: volatileCursor,
25503
25788
  force: opts?.force === true,
25504
25789
  limit
25505
25790
  }, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
@@ -25520,6 +25805,69 @@ var V2E2EECoordinator = class {
25520
25805
  client._delivery.onPullWorkSettled?.();
25521
25806
  return deduplicatePulledResults(decrypted);
25522
25807
  }
25808
+ async publishV2VolatileMessages(messages, source, advanceCursor) {
25809
+ const client = this.client;
25810
+ if (!client._v2Session) return [];
25811
+ const processed = [];
25812
+ const plaintextMessages = [];
25813
+ let complete = true;
25814
+ for (const rawMessage of messages) {
25815
+ const claim = client._delivery.beginVolatileDelivery(rawMessage);
25816
+ if (claim === "seen") {
25817
+ processed.push(rawMessage);
25818
+ continue;
25819
+ }
25820
+ if (claim === "inflight") {
25821
+ complete = false;
25822
+ continue;
25823
+ }
25824
+ let plaintext;
25825
+ try {
25826
+ if (client._isEncryptedPushMessage(rawMessage)) {
25827
+ plaintext = await client._decryptV2Message(rawMessage, true, true, true, true, void 0, false, source);
25828
+ } else {
25829
+ const legacy = isJsonObject(rawMessage.legacy_v1) ? rawMessage.legacy_v1 : {};
25830
+ plaintext = {
25831
+ message_id: String(rawMessage.message_id ?? ""),
25832
+ from: String(rawMessage.from_aid ?? rawMessage.from ?? ""),
25833
+ to: String(legacy.to ?? rawMessage.to ?? client._aid ?? ""),
25834
+ type: String(rawMessage.type ?? ""),
25835
+ timestamp: rawMessage.t_server ?? rawMessage.timestamp,
25836
+ payload: legacy.payload !== void 0 ? legacy.payload : rawMessage.payload,
25837
+ encrypted: false
25838
+ };
25839
+ for (const key of ["context", "protected_headers", "headers", "client_signature"]) {
25840
+ if (key in rawMessage) plaintext[key] = rawMessage[key];
25841
+ }
25842
+ if (rawMessage.sender_device_id !== void 0) plaintext.sender_device_id = rawMessage.sender_device_id;
25843
+ attachGatewayProximity(plaintext, rawMessage);
25844
+ }
25845
+ if (!plaintext) {
25846
+ complete = false;
25847
+ continue;
25848
+ }
25849
+ const publicMessage = client._delivery.publicVolatileMessage(plaintext);
25850
+ for (const key of ["storage", "volatile_cursor", "created_at", "expires_at", "persist"]) {
25851
+ if (key in rawMessage) publicMessage[key] = rawMessage[key];
25852
+ }
25853
+ const appEvent = client._delivery.p2pAppEventForMessage(publicMessage);
25854
+ await client._publishAppEvent(appEvent.event, appEvent.payload, source);
25855
+ client._delivery.markVolatileSeen(rawMessage);
25856
+ processed.push(rawMessage);
25857
+ plaintextMessages.push(publicMessage);
25858
+ } finally {
25859
+ client._delivery.finishVolatileDelivery(rawMessage);
25860
+ }
25861
+ }
25862
+ if (processed.length > 0) {
25863
+ await client._delivery.ackVolatileMessages(
25864
+ processed,
25865
+ true,
25866
+ advanceCursor && complete && processed.length === messages.length
25867
+ );
25868
+ }
25869
+ return plaintextMessages;
25870
+ }
25523
25871
  async confirmForwardP2PAck(upToSeq, inlineGeneration) {
25524
25872
  const ns = this.client._aid ? `p2p:${this.client._aid}` : "";
25525
25873
  try {
@@ -25534,34 +25882,41 @@ var V2E2EECoordinator = class {
25534
25882
  throw exc;
25535
25883
  }
25536
25884
  }
25537
- async ackV2(upToSeq, inlineGeneration) {
25538
- return this.ackV2WithConfirmation(upToSeq, inlineGeneration, false);
25885
+ async ackV2(upToSeq, inlineGeneration, volatileCursors = [], volatileIds = []) {
25886
+ return this.ackV2WithConfirmation(upToSeq, inlineGeneration, false, volatileCursors, volatileIds);
25539
25887
  }
25540
25888
  async ackV2ForForward(upToSeq, inlineGeneration) {
25541
25889
  return this.ackV2WithConfirmation(upToSeq, inlineGeneration, true);
25542
25890
  }
25543
- async ackV2WithConfirmation(upToSeq, inlineGeneration, requireServerWatermark) {
25891
+ async ackV2WithConfirmation(upToSeq, inlineGeneration, requireServerWatermark, volatileCursors = [], volatileIds = []) {
25544
25892
  const client = this.client;
25545
25893
  const ns = client._aid ? `p2p:${client._aid}` : "";
25546
25894
  let seq2 = upToSeq ?? (ns ? client._seqTracker.getContiguousSeq(ns) : 0);
25547
- if (typeof seq2 !== "number" || !Number.isSafeInteger(seq2) || seq2 <= 0) return { acked: 0 };
25548
- seq2 = client._clampAckSeq("message.v2.ack", "up_to_seq", ns, seq2);
25549
- if (!Number.isSafeInteger(seq2) || seq2 <= 0) return { acked: 0 };
25895
+ if (typeof seq2 !== "number" || !Number.isSafeInteger(seq2) || seq2 <= 0) seq2 = 0;
25896
+ if (seq2 <= 0 && volatileCursors.length === 0 && volatileIds.length === 0) return { acked: 0 };
25897
+ if (seq2 > 0) seq2 = client._clampAckSeq("message.v2.ack", "up_to_seq", ns, seq2);
25898
+ if ((!Number.isSafeInteger(seq2) || seq2 <= 0) && volatileCursors.length === 0 && volatileIds.length === 0) {
25899
+ return { acked: 0 };
25900
+ }
25550
25901
  const ackGeneration = inlineGeneration ?? client._delivery?.pendingP2PInlineAckGeneration?.(ns) ?? null;
25551
25902
  const v2SessionAtStart = client._v2Session;
25552
25903
  const v2RuntimeGenerationAtStart = Number(client._v2RuntimeGeneration ?? 0);
25553
- const raw = await client._callRawV2Rpc("message.v2.ack", { up_to_seq: seq2 });
25904
+ const raw = await client._callRawV2Rpc("message.v2.ack", {
25905
+ ...seq2 > 0 ? { up_to_seq: seq2 } : {},
25906
+ ...volatileCursors.length > 0 ? { volatile_cursors: volatileCursors } : {},
25907
+ ...volatileIds.length > 0 ? { volatile_ids: volatileIds } : {}
25908
+ });
25554
25909
  const result = isJsonObject(raw) ? { ...raw } : { result: raw };
25555
- const actualAckSeq = requireServerWatermark ? client._delivery?.resolveForwardAckSeq?.(result, seq2) ?? 0 : client._delivery?.resolveActualAckSeq?.(result, seq2) ?? seq2;
25556
- if (requireServerWatermark && actualAckSeq < seq2) {
25910
+ const actualAckSeq = seq2 > 0 ? requireServerWatermark ? client._delivery?.resolveForwardAckSeq?.(result, seq2) ?? 0 : client._delivery?.resolveActualAckSeq?.(result, seq2) ?? seq2 : 0;
25911
+ if (seq2 > 0 && requireServerWatermark && actualAckSeq < seq2) {
25557
25912
  throw new E2EEError(`message.v2.ack server ACK watermark ${actualAckSeq} is below requested ${seq2}`);
25558
25913
  }
25559
25914
  result.ack_seq = actualAckSeq;
25560
25915
  result.success = true;
25561
- if (ackGeneration !== null) {
25916
+ if (seq2 > 0 && ackGeneration !== null) {
25562
25917
  client._delivery?.completePendingP2PInlineAckUpTo?.(ns, actualAckSeq, ackGeneration);
25563
25918
  }
25564
- if (v2SessionAtStart && client._v2Session === v2SessionAtStart && Number(client._v2RuntimeGeneration ?? 0) === v2RuntimeGenerationAtStart) {
25919
+ if (seq2 > 0 && v2SessionAtStart && client._v2Session === v2SessionAtStart && Number(client._v2RuntimeGeneration ?? 0) === v2RuntimeGenerationAtStart) {
25565
25920
  try {
25566
25921
  const destroyed = await v2SessionAtStart.maybeDestroyOldSPKs(actualAckSeq);
25567
25922
  if (client._v2Session === v2SessionAtStart && Number(client._v2RuntimeGeneration ?? 0) === v2RuntimeGenerationAtStart && destroyed.length > 0) {
@@ -26525,7 +26880,6 @@ var V2E2EECoordinator = class {
26525
26880
  seq: msg.seq ?? null,
26526
26881
  timestamp: msg.timestamp ?? msg.t_server ?? null,
26527
26882
  device_id: msg.device_id ?? null,
26528
- slot_id: msg.slot_id ?? null,
26529
26883
  _decrypt_error: "encrypted push payload is not decryptable on raw push path",
26530
26884
  _decrypt_stage: "push_envelope"
26531
26885
  };
@@ -26566,10 +26920,11 @@ var V2E2EECoordinator = class {
26566
26920
  encrypted: true,
26567
26921
  e2ee: e2eeMeta
26568
26922
  };
26923
+ const senderDeviceId = String(aad.from_device ?? "").trim();
26924
+ if (senderDeviceId) result.sender_device_id = senderDeviceId;
26569
26925
  result.direction = fromAid && fromAid === client._aid ? "outbound_sync" : "inbound";
26570
26926
  if (msg.t_server !== void 0) result.t_server = msg.t_server;
26571
26927
  if (msg.device_id !== void 0) result.device_id = msg.device_id;
26572
- if (msg.slot_id !== void 0) result.slot_id = msg.slot_id;
26573
26928
  attachGatewayProximity(result, msg);
26574
26929
  if (group) {
26575
26930
  result.group_id = msg.group_id ?? aad.group_id ?? envelope.group_id ?? null;
@@ -26643,6 +26998,10 @@ var V2E2EECoordinator = class {
26643
26998
  if (peerDevices.length === 0) {
26644
26999
  throw new E2EEError(`V2 bootstrap: no devices found for ${to}`);
26645
27000
  }
27001
+ const targetDeviceId = String(opts.deviceId ?? "").trim();
27002
+ if (targetDeviceId && !peerDevices.some((dev) => String(dev.device_id ?? "").trim() === targetDeviceId)) {
27003
+ throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
27004
+ }
26646
27005
  let targetSet = useCache ? this.getTargetSetCache(to) : void 0;
26647
27006
  if (targetSet) {
26648
27007
  targetSet = this.cloneP2PTargetSet(targetSet);
@@ -26713,6 +27072,17 @@ var V2E2EECoordinator = class {
26713
27072
  });
26714
27073
  targetSet = this.cloneP2PTargetSet(targetSet);
26715
27074
  }
27075
+ if (targetDeviceId) {
27076
+ let peerTargetFound = false;
27077
+ targetSet.targets = targetSet.targets.filter((target) => {
27078
+ const isPeerTarget = target.aid === to && (target.deviceId === targetDeviceId || wrapPolicy?.scope === "aid" && !target.deviceId) && target.role !== "self_sync";
27079
+ if (isPeerTarget) peerTargetFound = true;
27080
+ return isPeerTarget || target.role === "self_sync";
27081
+ });
27082
+ if (!peerTargetFound) {
27083
+ throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
27084
+ }
27085
+ }
26716
27086
  const sender = await session.getSenderIdentity();
26717
27087
  const envelope = await encryptP2PMessage(
26718
27088
  sender,
@@ -27070,6 +27440,9 @@ function groupCacheIds(raw, groupId, explicitGroupAid) {
27070
27440
  var GroupStateCoordinator = class {
27071
27441
  constructor(runtime) {
27072
27442
  __publicField(this, "runtime");
27443
+ __publicField(this, "ownerTransferInflight", /* @__PURE__ */ new Set());
27444
+ __publicField(this, "ownerTransferCompletedSeq", /* @__PURE__ */ new Map());
27445
+ __publicField(this, "ownerTransferCompletedLegacy", /* @__PURE__ */ new Set());
27073
27446
  this.runtime = runtime;
27074
27447
  }
27075
27448
  get client() {
@@ -27102,6 +27475,7 @@ var GroupStateCoordinator = class {
27102
27475
  if (!groupId) {
27103
27476
  return;
27104
27477
  }
27478
+ this.handleOwnerTransferEvent(data, groupId, action);
27105
27479
  for (const cacheGroupId of groupCacheIds(rawGroupId, groupId, data.group_aid ?? data.groupAid)) {
27106
27480
  client._v2E2EE?.deleteBootstrapCacheEntry?.(`group:${cacheGroupId}`);
27107
27481
  }
@@ -27111,6 +27485,60 @@ var GroupStateCoordinator = class {
27111
27485
  }
27112
27486
  client._v2E2EE?.handleGroupChangedSpk?.(data, groupId, action);
27113
27487
  }
27488
+ handleOwnerTransferEvent(data, groupId, action) {
27489
+ const status = String(data.status ?? "").trim();
27490
+ if (action !== "owner_transfer_rekey_pending" && action !== "owner_transferred") return;
27491
+ const eventSeq = Number(data.event_seq ?? 0);
27492
+ const normalizedEventSeq = Number.isSafeInteger(eventSeq) && eventSeq > 0 ? eventSeq : 0;
27493
+ const transferKey = [groupId, data.old_owner, data.new_owner].map((value) => String(value ?? "").trim().toLowerCase()).join("|");
27494
+ if (action === "owner_transferred" && status === "transferred") {
27495
+ if (normalizedEventSeq > 0) {
27496
+ this.ownerTransferCompletedSeq.set(
27497
+ groupId,
27498
+ Math.max(normalizedEventSeq, this.ownerTransferCompletedSeq.get(groupId) ?? 0)
27499
+ );
27500
+ } else {
27501
+ this.ownerTransferCompletedLegacy.add(transferKey);
27502
+ }
27503
+ return;
27504
+ }
27505
+ if (action !== "owner_transfer_rekey_pending" || status !== "pending_rekey") return;
27506
+ const client = this.client;
27507
+ const currentAid = String(client._aid ?? "").trim();
27508
+ const newOwner = String(data.new_owner ?? "").trim();
27509
+ const store = client._aidStore;
27510
+ if (!currentAid || currentAid.toLowerCase() !== newOwner.toLowerCase() || !store) return;
27511
+ if (this.ownerTransferInflight.has(groupId)) return;
27512
+ if (normalizedEventSeq > 0) {
27513
+ if ((this.ownerTransferCompletedSeq.get(groupId) ?? 0) >= normalizedEventSeq) return;
27514
+ } else if (this.ownerTransferCompletedLegacy.has(transferKey)) {
27515
+ return;
27516
+ }
27517
+ this.ownerTransferInflight.add(groupId);
27518
+ const params2 = { group_id: groupId };
27519
+ const groupAid = String(data.group_aid ?? "").trim();
27520
+ if (groupAid) params2.group_aid = groupAid;
27521
+ const task = (async () => {
27522
+ try {
27523
+ await client.completeGroupTransfer(params2, { aidStore: store });
27524
+ if (normalizedEventSeq > 0) {
27525
+ this.ownerTransferCompletedSeq.set(
27526
+ groupId,
27527
+ Math.max(normalizedEventSeq, this.ownerTransferCompletedSeq.get(groupId) ?? 0)
27528
+ );
27529
+ } else {
27530
+ this.ownerTransferCompletedLegacy.add(transferKey);
27531
+ }
27532
+ } catch (exc) {
27533
+ client._clientLog.debug(
27534
+ `automatic group owner transfer completion failed: group=${groupId} err=${String(formatCaughtError(exc))}`
27535
+ );
27536
+ } finally {
27537
+ this.ownerTransferInflight.delete(groupId);
27538
+ }
27539
+ })();
27540
+ client._safeAsync(task);
27541
+ }
27114
27542
  async onV2StateProposed(data) {
27115
27543
  const client = this.client;
27116
27544
  if (!isJsonObject(data) || !client._v2Session) return;
@@ -28822,6 +29250,7 @@ var AgentMdManager = class _AgentMdManager {
28822
29250
  __publicField(this, "_uploadViaGatewayHost");
28823
29251
  __publicField(this, "_tokenStore");
28824
29252
  __publicField(this, "_log");
29253
+ __publicField(this, "_onChanged");
28825
29254
  __publicField(this, "_ownerAidGetter");
28826
29255
  __publicField(this, "_currentAidGetter");
28827
29256
  __publicField(this, "_gatewayResolver");
@@ -28839,6 +29268,7 @@ var AgentMdManager = class _AgentMdManager {
28839
29268
  this._uploadViaGatewayHost = opts.uploadViaGatewayHost ?? false;
28840
29269
  this._tokenStore = opts.tokenStore;
28841
29270
  this._log = opts.logger ?? noopLogger;
29271
+ this._onChanged = opts.onChanged;
28842
29272
  this._ownerAidGetter = opts.ownerAidGetter;
28843
29273
  this._currentAidGetter = opts.currentAidGetter;
28844
29274
  this._gatewayResolver = opts.gatewayResolver;
@@ -29042,6 +29472,9 @@ var AgentMdManager = class _AgentMdManager {
29042
29472
  let before = this._cache.get(target);
29043
29473
  if (!before || typeof before !== "object") before = await this.loadRecord(target) ?? {};
29044
29474
  const same = (!remoteEtag || String(before.remote_etag ?? "").trim() === remoteEtag) && (!remoteLastModified || String(before.last_modified ?? "").trim() === remoteLastModified);
29475
+ const hadRemoteVersion = Boolean(
29476
+ String(before.remote_etag ?? "").trim() || String(before.last_modified ?? "").trim()
29477
+ );
29045
29478
  let record = { ...before };
29046
29479
  if (!same || Object.keys(before).length === 0) {
29047
29480
  const fields = {
@@ -29050,7 +29483,9 @@ var AgentMdManager = class _AgentMdManager {
29050
29483
  };
29051
29484
  if (remoteEtag) fields.remote_etag = remoteEtag;
29052
29485
  if (remoteLastModified) fields.last_modified = remoteLastModified;
29053
- record = await this.saveRecord(target, fields) || record;
29486
+ const saved = await this.saveRecord(target, fields);
29487
+ record = saved || record;
29488
+ if (Object.keys(saved).length > 0 && hadRemoteVersion && !same) this._onChanged?.(target);
29054
29489
  }
29055
29490
  await this._scheduleFetchIfMissing(target, record, source);
29056
29491
  this._log.debug(`agent.md meta observed: aid=${target} etag=${remoteEtag || "-"} last_modified=${remoteLastModified || "-"} source=${source || "-"}`);
@@ -29606,6 +30041,7 @@ function createAgentMdManagerForRuntime(opts) {
29606
30041
  return new AgentMdManager({
29607
30042
  aunPath: opts.config().aunPath,
29608
30043
  tokenStore: opts.tokenStore(),
30044
+ onChanged: (aid) => opts.dispatcher().enqueue("agentmd.changed", aid),
29609
30045
  uploadViaGatewayHost: true,
29610
30046
  logger: opts.logger(),
29611
30047
  ownerAidGetter: opts.ownerAid,
@@ -30018,6 +30454,7 @@ var _AUNClient = class _AUNClient {
30018
30454
  this._agentMdManager = createAgentMdManagerForRuntime({
30019
30455
  config: () => this.configModel,
30020
30456
  logger: () => this._logger.for("aun_core.agent_md"),
30457
+ dispatcher: () => this._dispatcher,
30021
30458
  ownerAid: () => this._aid,
30022
30459
  currentAid: () => this._currentAid,
30023
30460
  gateway: {
@@ -30418,6 +30855,7 @@ var _AUNClient = class _AUNClient {
30418
30855
  this._agentMdManager = createAgentMdManagerForRuntime({
30419
30856
  config: () => this.configModel,
30420
30857
  logger: () => this._logger.for("aun_core.agent_md"),
30858
+ dispatcher: () => this._dispatcher,
30421
30859
  ownerAid: () => this._aid,
30422
30860
  currentAid: () => this._currentAid,
30423
30861
  gateway: {
@@ -30602,12 +31040,18 @@ var _AUNClient = class _AUNClient {
30602
31040
  for (const raw of groups) {
30603
31041
  if (!isJsonObject(raw)) continue;
30604
31042
  const role = String(raw.role ?? raw.my_role ?? "").trim().toLowerCase();
30605
- if (role !== "owner") continue;
30606
31043
  const groupId = String(raw.group_id ?? raw.groupId ?? raw.group_aid ?? raw.groupAid ?? "").trim();
30607
31044
  const groupAid = String(raw.group_aid ?? raw.groupAid ?? groupId).trim();
30608
31045
  if (!groupId || !groupAid) continue;
31046
+ const keystore = store._keystore;
31047
+ const pendingTransfer = keystore && typeof keystore.loadPendingGroupBind === "function" ? await keystore.loadPendingGroupBind(`complete:${groupId}`) : null;
31048
+ if (role !== "owner" && !pendingTransfer) continue;
30609
31049
  try {
30610
31050
  await this._runGroupIdentityOperation(groupId, async () => {
31051
+ if (pendingTransfer) {
31052
+ await this.completeGroupTransfer({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
31053
+ return;
31054
+ }
30611
31055
  const loaded = await store.load(groupAid);
30612
31056
  if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid()) {
30613
31057
  await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
@@ -30660,6 +31104,36 @@ var _AUNClient = class _AUNClient {
30660
31104
  if (!isNamed) {
30661
31105
  delete payload.public_key;
30662
31106
  delete payload.curve;
31107
+ const keystore2 = store._keystore;
31108
+ const pendingName = String(payload.name ?? "").trim().toLowerCase();
31109
+ const pendingNameHash = bytesToHex8(new Uint8Array(await globalThis.crypto.subtle.digest(
31110
+ "SHA-256",
31111
+ new TextEncoder().encode(pendingName)
31112
+ )));
31113
+ const pendingKey2 = `create-anonymous:${pendingNameHash}`;
31114
+ let keyPair2 = null;
31115
+ if (keystore2 && typeof keystore2.loadPendingGroupBind === "function") {
31116
+ keyPair2 = await keystore2.loadPendingGroupBind(pendingKey2);
31117
+ }
31118
+ if (!keyPair2 || !keyPair2.public_key_der_b64 || !keyPair2.private_key_pem) {
31119
+ const generated = await new CryptoProvider().generateIdentity();
31120
+ keyPair2 = {
31121
+ private_key_pem: generated.private_key_pem,
31122
+ public_key_der_b64: generated.public_key_der_b64,
31123
+ curve: generated.curve
31124
+ };
31125
+ if (keystore2 && typeof keystore2.savePendingGroupBind === "function") {
31126
+ await keystore2.savePendingGroupBind(pendingKey2, keyPair2);
31127
+ }
31128
+ }
31129
+ if (!keyPair2?.public_key_der_b64 || !keyPair2.private_key_pem) {
31130
+ throw new ValidationError("createGroup: failed to generate or load key pair");
31131
+ }
31132
+ const requestHash = bytesToHex8(new Uint8Array(await globalThis.crypto.subtle.digest(
31133
+ "SHA-256",
31134
+ new TextEncoder().encode(keyPair2.public_key_der_b64)
31135
+ )));
31136
+ payload._create_request_id = `anon:${requestHash}`;
30663
31137
  const created = await this.call("group.create", payload);
30664
31138
  const createdMap = isJsonObject(created) ? created : {};
30665
31139
  const createdGroup = isJsonObject(createdMap.group) ? createdMap.group : {};
@@ -30667,6 +31141,9 @@ var _AUNClient = class _AUNClient {
30667
31141
  if (!groupId) {
30668
31142
  throw new ValidationError("group.create response missing group.group_id");
30669
31143
  }
31144
+ if (keystore2 && typeof keystore2.savePendingGroupBind === "function") {
31145
+ await keystore2.savePendingGroupBind(`bind:${groupId}`, keyPair2);
31146
+ }
30670
31147
  const bindParams = { group_id: groupId };
30671
31148
  for (const key of ["name", "description", "group_description", "avatar", "group_agent_md", "groupAgentMd", "content"]) {
30672
31149
  if (key in payload) bindParams[key] = payload[key];
@@ -30680,15 +31157,26 @@ var _AUNClient = class _AUNClient {
30680
31157
  const result2 = { ...createdMap, ...boundMap, group: { ...createdGroup, ...boundGroup } };
30681
31158
  const postprocessParams2 = { ...payload };
30682
31159
  delete postprocessParams2._defer_group_ready_postprocess;
31160
+ delete postprocessParams2._create_request_id;
30683
31161
  const processed2 = await this._groupState.postprocessResult("group.create", postprocessParams2, result2);
30684
- return await this._inviteInitialGroupMembers(processed2, groupId, initialMembers);
31162
+ const completed = await this._inviteInitialGroupMembers(processed2, groupId, initialMembers);
31163
+ if (keystore2 && typeof keystore2.clearPendingGroupBind === "function") {
31164
+ await keystore2.clearPendingGroupBind(pendingKey2);
31165
+ }
31166
+ return completed;
30685
31167
  }
30686
- const pendingKey = `create:${String(payload.group_name).trim().toLowerCase()}`;
31168
+ const groupName = String(payload.group_name).trim().toLowerCase();
31169
+ const pendingKey = `create:${groupName}`;
30687
31170
  const keystore = store._keystore;
30688
31171
  let keyPair = null;
30689
31172
  if (keystore && typeof keystore.loadPendingGroupBind === "function") {
30690
31173
  keyPair = await keystore.loadPendingGroupBind(pendingKey);
30691
31174
  }
31175
+ if ((!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) && keystore && typeof keystore.loadIdentity === "function") {
31176
+ const ownerAid = String(this._aid ?? "").trim();
31177
+ const dot = ownerAid.indexOf(".");
31178
+ if (dot >= 0) keyPair = await keystore.loadIdentity(`${groupName}.${ownerAid.slice(dot + 1).toLowerCase()}`);
31179
+ }
30692
31180
  if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
30693
31181
  const generated = await new CryptoProvider().generateIdentity();
30694
31182
  keyPair = {
@@ -32785,8 +33273,8 @@ ${invitee}` : "";
32785
33273
  *
32786
33274
  * @param upToSeq 确认到此 seq;省略则用当前 contiguous
32787
33275
  */
32788
- async _ackV2(upToSeq, inlineGeneration) {
32789
- return await this._v2E2EE.ackV2(upToSeq, inlineGeneration);
33276
+ async _ackV2(upToSeq, inlineGeneration, volatileCursors = [], volatileIds = []) {
33277
+ return await this._v2E2EE.ackV2(upToSeq, inlineGeneration, volatileCursors, volatileIds);
32790
33278
  }
32791
33279
  /**
32792
33280
  * 解密单条 V2 消息(与 Python `_decrypt_v2_message` 对齐)。
@@ -32878,7 +33366,6 @@ ${invitee}` : "";
32878
33366
  seq: msg.seq,
32879
33367
  timestamp: msg.t_server ?? msg.timestamp,
32880
33368
  device_id: String(msg.device_id ?? ""),
32881
- slot_id: String(msg.slot_id ?? ""),
32882
33369
  _decrypt_error: String(exc),
32883
33370
  _decrypt_stage: "spk_lookup",
32884
33371
  _envelope_type: String(envelope.type ?? ""),
@@ -32921,7 +33408,6 @@ ${invitee}` : "";
32921
33408
  seq: msg.seq,
32922
33409
  timestamp: msg.t_server ?? msg.timestamp,
32923
33410
  device_id: String(msg.device_id ?? ""),
32924
- slot_id: String(msg.slot_id ?? ""),
32925
33411
  _decrypt_error: "sender_ik_not_found",
32926
33412
  _decrypt_stage: "sender_ik",
32927
33413
  _envelope_type: String(envelope.type ?? ""),
@@ -32958,7 +33444,6 @@ ${invitee}` : "";
32958
33444
  seq: msg.seq,
32959
33445
  timestamp: msg.t_server ?? msg.timestamp,
32960
33446
  device_id: String(msg.device_id ?? ""),
32961
- slot_id: String(msg.slot_id ?? ""),
32962
33447
  _decrypt_error: String(exc),
32963
33448
  _decrypt_stage: "decrypt",
32964
33449
  _envelope_type: String(envelope.type ?? ""),
@@ -32985,10 +33470,10 @@ ${invitee}` : "";
32985
33470
  encrypted: true,
32986
33471
  e2ee
32987
33472
  };
33473
+ if (senderDeviceId) result.sender_device_id = senderDeviceId;
32988
33474
  const explicitDirection = String(msg.direction ?? "").trim();
32989
33475
  result.direction = explicitDirection || (fromAid && fromAid === this._aid ? "outbound_sync" : "inbound");
32990
33476
  if (msg.device_id !== void 0) result.device_id = msg.device_id;
32991
- if (msg.slot_id !== void 0) result.slot_id = msg.slot_id;
32992
33477
  attachGatewayProximity2(result, msg);
32993
33478
  attachV2EnvelopeMetadata2(result, e2ee);
32994
33479
  if (groupIdForKeys) result = normalizeGroupMentionMode(result, msg, envelope);