@glyphteck/veyl 0.32.1 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6216,7 +6216,7 @@ var init_seed = __esm(() => {
6216
6216
  });
6217
6217
 
6218
6218
  // ../../shared/config.js
6219
- var MS_PER_SECOND = 1000, MINUTE_MS, HOUR_MS, DAY_MS, KIB_BYTES = 1024, MIB_BYTES, SATS_PER_BITCOIN = 100000000n, USERNAME_MAX_CHARS = 12, PASSWORD_MIN_CHARS = 12, PASSWORD_MAX_CHARS = 64, AUTOLOCK_MIN_MINUTES = 1, AUTOLOCK_MAX_MINUTES = 60, LOCAL_CACHE_WRITE_DELAY_MS = 350, LOCAL_MEDIA_CACHE_MAX_BYTES, LOCAL_MEDIA_CACHE_MAX_ITEMS = 1000, LOCAL_MEDIA_ACCESS_TOUCH_MIN_MS, LOCAL_PROFILE_CACHE_MAX_ITEMS = 500, LOCAL_PROFILE_CACHE_MAX_AGE_MS, LOCAL_CHAT_CACHE_MAX_ITEMS = 1000, LOCAL_CHAT_MESSAGE_CACHE_MAX_VISIBLE = 1000, LOCAL_AVATAR_CACHE_MAX_BYTES, LOCAL_AVATAR_CACHE_MAX_AGE_MS, AVATAR_IMAGE_MAX_BYTES, CHAT_READY_DOWNLOAD_MAX_BYTES, CHAT_MESSAGE_FILE_CACHE_MAX_BYTES, IDLE_CALLBACK_MIN_TIMEOUT_MS = 50, ATTACHMENT_CACHE_IDLE_TIMEOUT_MS = 2500, ATTACHMENT_CACHE_FALLBACK_DELAY_MS = 250, CHAT_UNSAVED_TTL_DAYS = 21, CHAT_UNSAVED_TTL_MS, CHAT_AFTER_SEEN_MS, CHAT_MEDIA_TTL_DAYS, CHAT_MEDIA_TTL_MS, CHAT_UPLOAD_MAX_BYTES, CHAT_AUDIO_TRANSCODE_BITRATE_BPS = 128000, CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS = 768000, CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS, CHAT_MESSAGE_BATCH_SIZE = 40, CHAT_MESSAGE_QUERY_MAX_DOCS = 120, CHAT_TTL_CLIENT_DELETE_GRACE_MS, CHAT_TTL_DELETE_CHUNK_SIZE = 40, CHAT_DELETE_SCAN_BATCH_SIZE = 200, CHAT_TTL_WRITE_BATCH_SIZE = 400, CHAT_DELETE_WRITE_BATCH_SIZE = 400, CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS = 1500, CHAT_BATCH_CLEANUP_IDLE_DELAY_MS = 250, CHAT_LIST_PAGE_SIZE = 20, CHAT_LIST_LIVE_COUNT, CHAT_INBOX_PING_PAGE_SIZE = 25, CHAT_LIST_CACHE_WRITE_DELAY_MS = 1500, CHAT_LIST_SNAPSHOT_COALESCE_MS = 80, CHAT_LIST_STATUS_CACHE_MS, CHAT_LIST_LISTENER_RETRY_MS = 1000, CHAT_TOP_WARM_COUNT = 0, CHAT_EAGER_WARM_COUNT = 0, CHAT_WARM_DELAY_MS = 3000, CHAT_WARM_BATCH_SIZE, CHAT_MESSAGE_VIEW_CACHE_SIZE = 30, CHAT_MEDIA_WARM_MESSAGES_PER_CHAT, CHAT_MEDIA_WARM_START_DELAY_MS = 600, CHAT_MEDIA_WARM_STEP_DELAY_MS = 120, CHAT_MEDIA_WARM_TYPES, CHAT_MEDIA_WARM_MAX_BYTES = 0, CHAT_READ_WRITE_INTERVAL_MS, CHAT_SEND_QUEUE_RATE_LIMIT_COUNT = 12, CHAT_SEND_QUEUE_RATE_LIMIT_WINDOW_MS, CHAT_MESSAGE_BODY_MAX_BYTES, CHAT_LAST_MESSAGE_BODY_MAX_BYTES, CHAT_SETTINGS_BODY_MAX_BYTES, BOT_WALLET_CLAIM_POLL_MS, BOT_WALLET_IDLE_CLAIM_POLL_MS, CHAT_MAX_TEXT_CHARS = 2048, CHAT_MAX_REACTIONS = 2, CHAT_PAIR_CACHE_LIMIT = 256, SEARCH_DEBOUNCE_MS = 300, SEARCH_USERNAME_LIMIT = 15, SEARCH_ROLE_LIMIT = 15, RECENT_PEER_REFRESH_LIMIT = 50, RECENT_PEER_REFRESH_DELAY_MS = 250, RECENT_PEER_REFRESH_INTERVAL_MS, RECENT_PEER_REFRESH_THROTTLE_MS = 120, BAN_REFRESH_GRACE_MS = 50, REQUEST_MONEY_MAX_SATS, WALLET_TRANSFER_POLL_MS, WALLET_ACTIVE_TRANSFER_REFRESH_MS, WALLET_IDLE_ACTIVE_TRANSFER_REFRESH_MS, WALLET_VISIBLE_TRANSFER_DISCOVERY_WINDOW_MS, WALLET_ACTIVE_TRANSFER_EMPTY_SCAN_LIMIT = 8, WALLET_CACHE_HYDRATE_DELAY_MS = 0, WALLET_BOOT_REFRESH_DELAY_MS = 500, WALLET_BOOT_CACHED_REFRESH_DELAY_MS = 2500, WALLET_REGTEST_DEPOSIT_CLAIM_POLL_MS, WALLET_MAINNET_DEPOSIT_CLAIM_POLL_MS, WALLET_BALANCE_EVENT_COALESCE_MS, WALLET_INCOMING_UPDATE_COALESCE_MS = 250, WALLET_AUTO_CLAIM_MAX_FEE_SATS = 5000, WALLET_CLAIM_PAGE_SIZE = 100, WALLET_PENDING_TRANSFER_CLAIM_BATCH_SIZE = 50, WALLET_PENDING_TRANSFER_STATUS_BATCH_SIZE = 2, WALLET_PENDING_TRANSFER_COLD_REFRESH_BATCH_SIZE = 50, WALLET_PENDING_TRANSFER_COLD_REFRESH_INTERVAL_MS, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_QUEUE = 3, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_QUEUE = 10, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_SIZE = 2, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_SIZE = 3, WALLET_PENDING_TRANSFER_SLOW_CLAIM_MS, WALLET_PENDING_TRANSFER_CLAIM_COOLDOWN_MS, WALLET_SDK_BACKGROUND_QUIET_MS, WALLET_RECENT_TRANSFER_LIMIT = 100, WALLET_TRANSFER_CACHE_LIMIT = 1e4, WALLET_TRANSFER_PAGE_LIMIT = 100, WALLET_TRANSFER_FETCH_THROTTLE_MS = 150, WALLET_PENDING_TRANSFER_CLAIM_RETRY_MS, WALLET_SENT_TRANSFER_REFRESH_DELAY_MS, WALLET_PENDING_TRANSFER_BOOT_GRACE_MS, WALLET_PENDING_TRANSFER_HOT_AGE_MS, WALLET_PENDING_TRANSFER_WARM_AGE_MS, WALLET_PENDING_TRANSFER_WARM_RETRY_MS, WALLET_PENDING_TRANSFER_STALE_RETRY_MS, WALLET_PENDING_TRANSFER_STUCK_RETRY_MS, WALLET_PENDING_TRANSFER_DORMANT_RETRY_MS, WALLET_TRANSFER_CACHE_WRITE_DELAY_MS;
6219
+ var MS_PER_SECOND = 1000, MINUTE_MS, HOUR_MS, DAY_MS, KIB_BYTES = 1024, MIB_BYTES, SATS_PER_BITCOIN = 100000000n, USERNAME_MAX_CHARS = 12, PASSWORD_MIN_CHARS = 12, PASSWORD_MAX_CHARS = 64, AUTOLOCK_MIN_MINUTES = 1, AUTOLOCK_MAX_MINUTES = 60, LOCAL_CACHE_WRITE_DELAY_MS = 350, LOCAL_MEDIA_CACHE_MAX_BYTES, LOCAL_MEDIA_CACHE_MAX_ITEMS = 1000, LOCAL_MEDIA_ACCESS_TOUCH_MIN_MS, LOCAL_PROFILE_CACHE_MAX_ITEMS = 500, LOCAL_PROFILE_CACHE_MAX_AGE_MS, LOCAL_CHAT_CACHE_MAX_ITEMS = 1000, LOCAL_CHAT_MESSAGE_CACHE_MAX_VISIBLE = 1000, LOCAL_AVATAR_CACHE_MAX_BYTES, LOCAL_AVATAR_CACHE_MAX_AGE_MS, AVATAR_IMAGE_MAX_BYTES, CHAT_READY_DOWNLOAD_MAX_BYTES, CHAT_MESSAGE_FILE_CACHE_MAX_BYTES, IDLE_CALLBACK_MIN_TIMEOUT_MS = 50, ATTACHMENT_CACHE_IDLE_TIMEOUT_MS = 2500, ATTACHMENT_CACHE_FALLBACK_DELAY_MS = 250, CHAT_UNSAVED_TTL_DAYS = 21, CHAT_UNSAVED_TTL_MS, CHAT_AFTER_SEEN_MS, CHAT_MEDIA_TTL_DAYS, CHAT_MEDIA_TTL_MS, CHAT_UPLOAD_MAX_BYTES, CHAT_AUDIO_TRANSCODE_BITRATE_BPS = 128000, CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS = 3000000, CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS, CHAT_MESSAGE_BATCH_SIZE = 40, CHAT_MESSAGE_QUERY_MAX_DOCS = 120, CHAT_TTL_CLIENT_DELETE_GRACE_MS, CHAT_TTL_DELETE_CHUNK_SIZE = 40, CHAT_DELETE_SCAN_BATCH_SIZE = 200, CHAT_TTL_WRITE_BATCH_SIZE = 400, CHAT_DELETE_WRITE_BATCH_SIZE = 400, CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS = 1500, CHAT_BATCH_CLEANUP_IDLE_DELAY_MS = 250, CHAT_LIST_PAGE_SIZE = 20, CHAT_LIST_LIVE_COUNT, CHAT_INBOX_PING_PAGE_SIZE = 25, CHAT_LIST_CACHE_WRITE_DELAY_MS = 1500, CHAT_LIST_SNAPSHOT_COALESCE_MS = 80, CHAT_LIST_STATUS_CACHE_MS, CHAT_LIST_LISTENER_RETRY_MS = 1000, CHAT_TOP_WARM_COUNT = 0, CHAT_EAGER_WARM_COUNT = 0, CHAT_WARM_DELAY_MS = 3000, CHAT_WARM_BATCH_SIZE, CHAT_MESSAGE_VIEW_CACHE_SIZE = 30, CHAT_MEDIA_WARM_MESSAGES_PER_CHAT, CHAT_MEDIA_WARM_START_DELAY_MS = 600, CHAT_MEDIA_WARM_STEP_DELAY_MS = 120, CHAT_MEDIA_WARM_TYPES, CHAT_MEDIA_WARM_MAX_BYTES = 0, CHAT_READ_WRITE_INTERVAL_MS, CHAT_SEND_QUEUE_RATE_LIMIT_COUNT = 12, CHAT_SEND_QUEUE_RATE_LIMIT_WINDOW_MS, CHAT_MESSAGE_BODY_MAX_BYTES, CHAT_LAST_MESSAGE_BODY_MAX_BYTES, CHAT_SETTINGS_BODY_MAX_BYTES, BOT_WALLET_CLAIM_POLL_MS, BOT_WALLET_IDLE_CLAIM_POLL_MS, CHAT_MAX_TEXT_CHARS = 2048, CHAT_MAX_REACTIONS = 2, CHAT_PAIR_CACHE_LIMIT = 256, SEARCH_DEBOUNCE_MS = 300, SEARCH_USERNAME_LIMIT = 15, SEARCH_ROLE_LIMIT = 15, RECENT_PEER_REFRESH_LIMIT = 50, RECENT_PEER_REFRESH_DELAY_MS = 250, RECENT_PEER_REFRESH_INTERVAL_MS, RECENT_PEER_REFRESH_THROTTLE_MS = 120, BAN_REFRESH_GRACE_MS = 50, REQUEST_MONEY_MAX_SATS, WALLET_TRANSFER_POLL_MS, WALLET_ACTIVE_TRANSFER_REFRESH_MS, WALLET_IDLE_ACTIVE_TRANSFER_REFRESH_MS, WALLET_VISIBLE_TRANSFER_DISCOVERY_WINDOW_MS, WALLET_ACTIVE_TRANSFER_EMPTY_SCAN_LIMIT = 8, WALLET_CACHE_HYDRATE_DELAY_MS = 0, WALLET_BOOT_REFRESH_DELAY_MS = 500, WALLET_BOOT_CACHED_REFRESH_DELAY_MS = 2500, WALLET_REGTEST_DEPOSIT_CLAIM_POLL_MS, WALLET_MAINNET_DEPOSIT_CLAIM_POLL_MS, WALLET_BALANCE_EVENT_COALESCE_MS, WALLET_INCOMING_UPDATE_COALESCE_MS = 250, WALLET_AUTO_CLAIM_MAX_FEE_SATS = 5000, WALLET_CLAIM_PAGE_SIZE = 100, WALLET_PENDING_TRANSFER_CLAIM_BATCH_SIZE = 50, WALLET_PENDING_TRANSFER_STATUS_BATCH_SIZE = 2, WALLET_PENDING_TRANSFER_COLD_REFRESH_BATCH_SIZE = 50, WALLET_PENDING_TRANSFER_COLD_REFRESH_INTERVAL_MS, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_QUEUE = 3, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_QUEUE = 10, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_SIZE = 2, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_SIZE = 3, WALLET_PENDING_TRANSFER_SLOW_CLAIM_MS, WALLET_PENDING_TRANSFER_CLAIM_COOLDOWN_MS, WALLET_SDK_BACKGROUND_QUIET_MS, WALLET_RECENT_TRANSFER_LIMIT = 100, WALLET_TRANSFER_CACHE_LIMIT = 1e4, WALLET_TRANSFER_PAGE_LIMIT = 100, WALLET_TRANSFER_FETCH_THROTTLE_MS = 150, WALLET_PENDING_TRANSFER_CLAIM_RETRY_MS, WALLET_SENT_TRANSFER_REFRESH_DELAY_MS, WALLET_PENDING_TRANSFER_BOOT_GRACE_MS, WALLET_PENDING_TRANSFER_HOT_AGE_MS, WALLET_PENDING_TRANSFER_WARM_AGE_MS, WALLET_PENDING_TRANSFER_WARM_RETRY_MS, WALLET_PENDING_TRANSFER_STALE_RETRY_MS, WALLET_PENDING_TRANSFER_STUCK_RETRY_MS, WALLET_PENDING_TRANSFER_DORMANT_RETRY_MS, WALLET_TRANSFER_CACHE_WRITE_DELAY_MS;
6220
6220
  var init_config = __esm(() => {
6221
6221
  MINUTE_MS = 60 * MS_PER_SECOND;
6222
6222
  HOUR_MS = 60 * MINUTE_MS;
@@ -6227,7 +6227,7 @@ var init_config = __esm(() => {
6227
6227
  LOCAL_PROFILE_CACHE_MAX_AGE_MS = 30 * DAY_MS;
6228
6228
  LOCAL_AVATAR_CACHE_MAX_BYTES = 128 * MIB_BYTES;
6229
6229
  LOCAL_AVATAR_CACHE_MAX_AGE_MS = 30 * DAY_MS;
6230
- AVATAR_IMAGE_MAX_BYTES = 8 * MIB_BYTES;
6230
+ AVATAR_IMAGE_MAX_BYTES = 256 * KIB_BYTES;
6231
6231
  CHAT_READY_DOWNLOAD_MAX_BYTES = 8 * MIB_BYTES;
6232
6232
  CHAT_MESSAGE_FILE_CACHE_MAX_BYTES = 64 * MIB_BYTES;
6233
6233
  CHAT_UNSAVED_TTL_MS = CHAT_UNSAVED_TTL_DAYS * DAY_MS;
@@ -7743,20 +7743,17 @@ function expectedSigningKeys(pair, signingKeysByChatKey) {
7743
7743
  }
7744
7744
  return values;
7745
7745
  }
7746
- async function verifyExpectedSignatureBeforeOpen(pair, sig, bytes, options = {}) {
7746
+ async function preverifyExpectedSigner(pair, sig, bytes, options = {}) {
7747
7747
  if (!options?.crypto?.preverifyBeforeOpen) {
7748
- return true;
7749
- }
7750
- const signingKeysByChatKey = expectedSigningKeys(pair, options?.signingKeysByChatKey);
7751
- if (signingKeysByChatKey.length < 2) {
7752
- return true;
7748
+ return "";
7753
7749
  }
7754
- for (const signingPublicKey of signingKeysByChatKey) {
7750
+ const signingPublicKeys = expectedSigningKeys(pair, options?.signingKeysByChatKey);
7751
+ for (const signingPublicKey of signingPublicKeys) {
7755
7752
  if (await verifyMsgSignature(signingPublicKey, sig, bytes, options)) {
7756
- return true;
7753
+ return signingPublicKey;
7757
7754
  }
7758
7755
  }
7759
- return false;
7756
+ return null;
7760
7757
  }
7761
7758
  async function sealMsg(pair, message, options = {}) {
7762
7759
  const head = getHead(pair, message);
@@ -7777,14 +7774,16 @@ async function openMsg(pair, data, options = {}) {
7777
7774
  const { suite, flags, nonce, sig, ct } = unpackMsgBody(data.body);
7778
7775
  const aad = getMsgAad(pair, head, suite, flags);
7779
7776
  const signatureInput = getSignatureInput(aad, nonce, ct);
7780
- if (!await verifyExpectedSignatureBeforeOpen(pair, sig, signatureInput, options)) {
7777
+ const preverifiedSigner = await preverifyExpectedSigner(pair, sig, signatureInput, options);
7778
+ if (preverifiedSigner == null) {
7781
7779
  throw new Error("invalid chat action signature");
7782
7780
  }
7783
7781
  const plaintext = await openMsgPlaintext(pair, nonce, ct, aad, options);
7784
7782
  try {
7785
7783
  const action = JSON.parse(decoder.decode(plaintext));
7786
7784
  const normalized = normalizeOpenedAction(pair, head, action, options);
7787
- if (!await verifyMsgSignature(normalized.signingPublicKey, sig, signatureInput, options)) {
7785
+ const validSignature = preverifiedSigner ? normalized.signingPublicKey === preverifiedSigner : await verifyMsgSignature(normalized.signingPublicKey, sig, signatureInput, options);
7786
+ if (!validSignature) {
7788
7787
  throw new Error("invalid chat action signature");
7789
7788
  }
7790
7789
  return {
@@ -7858,40 +7857,120 @@ var init_protocol = __esm(() => {
7858
7857
  });
7859
7858
 
7860
7859
  // ../../shared/chat/pairs.js
7860
+ function accountGeneration(chatPK) {
7861
+ return accountGenerations.get(cleanText(chatPK)) || 0;
7862
+ }
7861
7863
  function getChatPairKey(chatPK, peerChatPK, chatId = "") {
7862
7864
  if (!chatPK || !peerChatPK)
7863
7865
  return null;
7864
7866
  return `${cleanText(chatPK)}|${cleanText(peerChatPK)}|${cleanText(chatId)}`;
7865
7867
  }
7866
- function clearChatPairCache() {
7867
- for (const pair of pairCache.values()) {
7868
- closeChatPair(pair);
7868
+ function closeEntry(entry) {
7869
+ if (!entry || entry.closed || entry.leases > 0) {
7870
+ return;
7869
7871
  }
7870
- pairCache.clear();
7872
+ entry.closed = true;
7873
+ closeChatPair(entry.pair);
7871
7874
  }
7872
- async function getCachedPair(chatPK, chatPrivKey, peerChatPK, options = {}) {
7875
+ function retireEntry(entry) {
7876
+ if (!entry || entry.retired) {
7877
+ return;
7878
+ }
7879
+ entry.retired = true;
7880
+ closeEntry(entry);
7881
+ }
7882
+ function releaseEntry(entry) {
7883
+ if (!entry || entry.leases <= 0) {
7884
+ return;
7885
+ }
7886
+ entry.leases -= 1;
7887
+ if (entry.retired) {
7888
+ closeEntry(entry);
7889
+ }
7890
+ }
7891
+ function evictOverflow() {
7892
+ while (pairCache.size > MAX_PAIR_CACHE) {
7893
+ const firstKey = pairCache.keys().next().value;
7894
+ if (!firstKey) {
7895
+ return;
7896
+ }
7897
+ const entry = pairCache.get(firstKey);
7898
+ pairCache.delete(firstKey);
7899
+ retireEntry(entry);
7900
+ }
7901
+ }
7902
+ function clearChatPairCache(chatPK = "") {
7903
+ const account = cleanText(chatPK);
7904
+ if (!account) {
7905
+ cacheGeneration += 1;
7906
+ accountGenerations.clear();
7907
+ for (const entry of pairCache.values()) {
7908
+ retireEntry(entry);
7909
+ }
7910
+ pairCache.clear();
7911
+ return;
7912
+ }
7913
+ accountGenerations.set(account, accountGeneration(account) + 1);
7914
+ for (const [key, entry] of pairCache) {
7915
+ if (entry.account !== account) {
7916
+ continue;
7917
+ }
7918
+ pairCache.delete(key);
7919
+ retireEntry(entry);
7920
+ }
7921
+ }
7922
+ async function acquireCachedPair(chatPK, chatPrivKey, peerChatPK, options = {}) {
7923
+ const account = cleanText(chatPK);
7873
7924
  const chatId = cleanText(options?.chatId);
7874
7925
  const key = getChatPairKey(chatPK, peerChatPK, chatId);
7926
+ const generation = cacheGeneration;
7927
+ const accountVersion = accountGeneration(account);
7875
7928
  if (!key) {
7876
- return openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
7929
+ const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
7930
+ const entry2 = { pair, leases: 1, retired: true, closed: false };
7931
+ return {
7932
+ pair,
7933
+ release: () => releaseEntry(entry2)
7934
+ };
7877
7935
  }
7878
- const cached = pairCache.get(key);
7879
- if (cached)
7880
- return cached;
7881
- const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
7882
- pairCache.set(key, pair);
7883
- if (pairCache.size > MAX_PAIR_CACHE) {
7884
- const firstKey = pairCache.keys().next().value;
7885
- if (firstKey) {
7886
- closeChatPair(pairCache.get(firstKey));
7887
- pairCache.delete(firstKey);
7936
+ let entry = pairCache.get(key);
7937
+ if (!entry) {
7938
+ const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
7939
+ if (generation !== cacheGeneration || accountVersion !== accountGeneration(account)) {
7940
+ const staleEntry = { pair, leases: 1, retired: true, closed: false };
7941
+ return {
7942
+ pair,
7943
+ release: () => releaseEntry(staleEntry)
7944
+ };
7945
+ }
7946
+ entry = pairCache.get(key);
7947
+ if (entry) {
7948
+ closeChatPair(pair);
7949
+ } else {
7950
+ entry = { account, pair, leases: 0, retired: false, closed: false };
7951
+ pairCache.set(key, entry);
7952
+ evictOverflow();
7888
7953
  }
7954
+ } else {
7955
+ pairCache.delete(key);
7956
+ pairCache.set(key, entry);
7957
+ }
7958
+ entry.leases += 1;
7959
+ return {
7960
+ pair: entry.pair,
7961
+ release: () => releaseEntry(entry)
7962
+ };
7963
+ }
7964
+ async function withCachedPair(chatPK, chatPrivKey, peerChatPK, options, runWithPair) {
7965
+ const { pair, release } = await acquireCachedPair(chatPK, chatPrivKey, peerChatPK, options);
7966
+ try {
7967
+ return await runWithPair(pair);
7968
+ } finally {
7969
+ release();
7889
7970
  }
7890
- return pair;
7891
7971
  }
7892
7972
  async function resolveLinkId(chatPK, chatPrivKey, peerChatPK) {
7893
- const pair = await getCachedPair(chatPK, chatPrivKey, peerChatPK);
7894
- return pair?.linkId || null;
7973
+ return withCachedPair(chatPK, chatPrivKey, peerChatPK, {}, (pair) => pair?.linkId || null);
7895
7974
  }
7896
7975
  async function resolvePeerChat(cloud, chatPK, chatPrivateKey, peerChatPK, options = {}) {
7897
7976
  const existingChatId = cleanText(options?.chatId);
@@ -7922,13 +8001,14 @@ async function resolvePeerChat(cloud, chatPK, chatPrivateKey, peerChatPK, option
7922
8001
  exists: linkChat?.exists === true
7923
8002
  };
7924
8003
  }
7925
- var pairCache, MAX_PAIR_CACHE;
8004
+ var pairCache, MAX_PAIR_CACHE, cacheGeneration = 0, accountGenerations;
7926
8005
  var init_pairs = __esm(() => {
7927
8006
  init_config();
7928
8007
  init_chat();
7929
8008
  init_protocol();
7930
8009
  pairCache = new Map;
7931
8010
  MAX_PAIR_CACHE = CHAT_PAIR_CACHE_LIMIT;
8011
+ accountGenerations = new Map;
7932
8012
  });
7933
8013
 
7934
8014
  // ../../shared/utils/diagnostics.js
@@ -8081,14 +8161,16 @@ function lockWallet(wallet) {
8081
8161
  cleanup?.catch?.(() => {});
8082
8162
  } catch {}
8083
8163
  }
8084
- function lockChat(chatPrivateKey) {
8164
+ function lockChat(chatPrivateKey, chatPK = "") {
8085
8165
  try {
8086
8166
  if (chatPrivateKey) {
8087
8167
  chatPrivateKey.fill(0);
8088
8168
  }
8089
8169
  } catch {}
8090
8170
  try {
8091
- clearChatPairCache();
8171
+ if (chatPK) {
8172
+ clearChatPairCache(chatPK);
8173
+ }
8092
8174
  } catch {}
8093
8175
  }
8094
8176
  var init_vault = __esm(() => {
@@ -8108,7 +8190,7 @@ function walletPubkey(value) {
8108
8190
  }
8109
8191
  return key;
8110
8192
  }
8111
- function closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner } = {}) {
8193
+ function closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner } = {}) {
8112
8194
  try {
8113
8195
  vaultAccess?.close?.();
8114
8196
  } catch {}
@@ -8119,7 +8201,7 @@ function closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess
8119
8201
  localCache?.close?.();
8120
8202
  } catch {}
8121
8203
  lockWallet(wallet);
8122
- lockChat(chatPrivateKey);
8204
+ lockChat(chatPrivateKey, chatPK);
8123
8205
  }
8124
8206
  function createAccountSession(resources = {}) {
8125
8207
  let closed = false;
@@ -8210,6 +8292,32 @@ function notifySeedDecrypted(callback, markDiag2, source) {
8210
8292
  failed(error);
8211
8293
  }
8212
8294
  }
8295
+ function startWalletBoot(bootWallet2, walletMnemonic, user, options) {
8296
+ let closed = false;
8297
+ let handedOff = false;
8298
+ let wallet = null;
8299
+ const result = Promise.resolve().then(() => bootWallet2(walletMnemonic, user, options)).then((identity) => {
8300
+ wallet = identity?.wallet || null;
8301
+ if (closed && wallet) {
8302
+ lockWallet(wallet);
8303
+ }
8304
+ return identity;
8305
+ });
8306
+ return {
8307
+ result,
8308
+ handoff() {
8309
+ handedOff = true;
8310
+ },
8311
+ close() {
8312
+ if (closed)
8313
+ return;
8314
+ closed = true;
8315
+ if (wallet && !handedOff) {
8316
+ lockWallet(wallet);
8317
+ }
8318
+ }
8319
+ };
8320
+ }
8213
8321
  async function openVaultAccountSession(vault, password, options = {}) {
8214
8322
  const {
8215
8323
  uid,
@@ -8251,6 +8359,7 @@ async function openVaultAccountSession(vault, password, options = {}) {
8251
8359
  throw new Error("unlockSettings missing");
8252
8360
  const { decryptSeed: decryptSeed2, unpackVaultSeedData } = vaultCrypto;
8253
8361
  let wallet = null;
8362
+ let chatPK = "";
8254
8363
  let chatPrivateKey = null;
8255
8364
  let localCache = null;
8256
8365
  let masterSeed = null;
@@ -8261,6 +8370,7 @@ async function openVaultAccountSession(vault, password, options = {}) {
8261
8370
  let vaultSigner = null;
8262
8371
  let vaultAccess = null;
8263
8372
  let vaultAccessResult = null;
8373
+ let walletBoot = null;
8264
8374
  let network;
8265
8375
  try {
8266
8376
  onStage?.("unlocking");
@@ -8329,46 +8439,18 @@ async function openVaultAccountSession(vault, password, options = {}) {
8329
8439
  onStage?.("wallet");
8330
8440
  const walletStartedAt = Date.now();
8331
8441
  mark(diag, "vault.unlock.wallet.start", { source });
8332
- const walletIdentity = await bootWallet2(walletMnemonic, user, { network, diag });
8333
- wallet = walletIdentity?.wallet;
8334
- const walletPK = walletPubkey(walletIdentity?.walletPK);
8335
- mark(diag, "vault.unlock.wallet.done", { elapsedMs: Date.now() - walletStartedAt, source });
8336
- requireCurrent(isCurrent);
8442
+ walletBoot = startWalletBoot(bootWallet2, walletMnemonic, user, { network, diag });
8337
8443
  onStage?.("chat");
8338
8444
  const chatStartedAt = Date.now();
8339
8445
  mark(diag, "vault.unlock.chat.start", { source });
8340
8446
  const chatIdentity = await bootChat2(chatSeed, user, { diag });
8341
8447
  chatPrivateKey = chatIdentity?.chatPrivateKey;
8342
- const chatPK = cleanText(chatIdentity?.chatPK);
8448
+ chatPK = cleanText(chatIdentity?.chatPK);
8343
8449
  if (!chatPrivateKey || !chatPK)
8344
8450
  throw new Error("chat identity missing");
8345
8451
  chatSeed = null;
8346
8452
  mark(diag, "vault.unlock.chat.done", { elapsedMs: Date.now() - chatStartedAt, source });
8347
8453
  requireCurrent(isCurrent);
8348
- const identityPayload = {};
8349
- if (walletIdentity?.walletPKNeedsWrite) {
8350
- identityPayload.walletPK = walletPK;
8351
- identityPayload.network = network;
8352
- }
8353
- if (chatIdentity?.chatPKNeedsWrite) {
8354
- identityPayload.chatPK = chatPK;
8355
- }
8356
- const identityFields = Object.keys(identityPayload);
8357
- let identityResult = null;
8358
- if (identityFields.length) {
8359
- const identitiesStartedAt = Date.now();
8360
- mark(diag, "vault.unlock.identities.start", { source, fields: identityFields });
8361
- identityResult = cloud.user.profile.identity.write(identityPayload).then(() => {
8362
- mark(diag, "vault.unlock.identities.done", {
8363
- elapsedMs: Date.now() - identitiesStartedAt,
8364
- source,
8365
- fields: identityFields
8366
- });
8367
- return { error: null };
8368
- }, (error) => ({ error }));
8369
- } else {
8370
- mark(diag, "vault.unlock.identities.skip", { source });
8371
- }
8372
8454
  const cacheStartedAt = Date.now();
8373
8455
  mark(diag, "vault.unlock.localCache.start", { source });
8374
8456
  localCache = await openLocalCache(cacheKey, { uid, network });
@@ -8376,20 +8458,16 @@ async function openVaultAccountSession(vault, password, options = {}) {
8376
8458
  cleanBytes(cacheKey);
8377
8459
  cacheKey = null;
8378
8460
  requireCurrent(isCurrent);
8379
- const identityWrite = await identityResult;
8380
- if (identityWrite?.error)
8381
- throw identityWrite.error;
8382
- requireCurrent(isCurrent);
8383
8461
  const signatureResult = await vaultAccessResult;
8384
8462
  if (signatureResult.error)
8385
8463
  throw signatureResult.error;
8386
8464
  vaultAccess = signatureResult.access;
8387
8465
  requireCurrent(isCurrent);
8388
- return createAccountSession({
8466
+ const session = createAccountSession({
8389
8467
  uid,
8390
8468
  network,
8391
- wallet,
8392
- walletPK,
8469
+ wallet: null,
8470
+ walletPK: null,
8393
8471
  chatPK,
8394
8472
  chatPrivateKey,
8395
8473
  localCache,
@@ -8397,12 +8475,56 @@ async function openVaultAccountSession(vault, password, options = {}) {
8397
8475
  vaultAccess,
8398
8476
  vaultPK: vaultSigner.publicKey
8399
8477
  });
8478
+ session.own(walletBoot);
8479
+ const walletReady = walletBoot.result.then(async (walletIdentity) => {
8480
+ if (session.closed)
8481
+ throw new Error("account session closed");
8482
+ const readyWallet = walletIdentity?.wallet;
8483
+ const walletPK = walletPubkey(walletIdentity?.walletPK);
8484
+ if (!readyWallet)
8485
+ throw new Error("wallet missing");
8486
+ mark(diag, "vault.unlock.wallet.done", { elapsedMs: Date.now() - walletStartedAt, source });
8487
+ const identityPayload = {};
8488
+ if (walletIdentity?.walletPKNeedsWrite) {
8489
+ identityPayload.walletPK = walletPK;
8490
+ identityPayload.network = network;
8491
+ }
8492
+ if (chatIdentity?.chatPKNeedsWrite) {
8493
+ identityPayload.chatPK = chatPK;
8494
+ }
8495
+ const identityFields = Object.keys(identityPayload);
8496
+ if (identityFields.length) {
8497
+ const identitiesStartedAt = Date.now();
8498
+ mark(diag, "vault.unlock.identities.start", { source, fields: identityFields });
8499
+ await cloud.user.profile.identity.write(identityPayload);
8500
+ mark(diag, "vault.unlock.identities.done", {
8501
+ elapsedMs: Date.now() - identitiesStartedAt,
8502
+ source,
8503
+ fields: identityFields
8504
+ });
8505
+ } else {
8506
+ mark(diag, "vault.unlock.identities.skip", { source });
8507
+ }
8508
+ if (session.closed)
8509
+ throw new Error("account session closed");
8510
+ walletBoot.handoff();
8511
+ session.wallet = readyWallet;
8512
+ session.walletPK = walletPK;
8513
+ return { wallet: readyWallet, walletPK };
8514
+ }).catch((error) => {
8515
+ walletBoot.close();
8516
+ throw error;
8517
+ });
8518
+ walletReady.catch(() => {});
8519
+ session.walletReady = walletReady;
8520
+ return session;
8400
8521
  } catch (error) {
8522
+ walletBoot?.close();
8401
8523
  if (vaultAccessResult && !vaultAccess) {
8402
8524
  const signatureResult = await vaultAccessResult;
8403
8525
  vaultAccess = signatureResult.access;
8404
8526
  }
8405
- closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner });
8527
+ closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner });
8406
8528
  throw error;
8407
8529
  } finally {
8408
8530
  cleanBytes(masterSeed, walletEntropy, chatSeed, cacheKey, settingsKey);
@@ -8417,6 +8539,7 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
8417
8539
  }
8418
8540
  const walletMnemonic = mnemonicFromWalletEntropy(walletEntropy);
8419
8541
  let wallet = null;
8542
+ let chatPK = "";
8420
8543
  let chatPrivateKey = null;
8421
8544
  let chatPubKey = null;
8422
8545
  let vaultSigner = null;
@@ -8445,10 +8568,11 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
8445
8568
  throw new Error("vault signature uid missing");
8446
8569
  vaultAccess = await cloud.auth.vaultSignature.open(vaultSigner);
8447
8570
  }
8571
+ chatPK = toHex(chatPubKey);
8448
8572
  return createAccountSession({
8449
8573
  wallet,
8450
8574
  walletPK,
8451
- chatPK: toHex(chatPubKey),
8575
+ chatPK,
8452
8576
  chatPrivateKey,
8453
8577
  localCache,
8454
8578
  network,
@@ -8457,7 +8581,7 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
8457
8581
  vaultPK: vaultSigner.publicKey
8458
8582
  });
8459
8583
  } catch (error) {
8460
- closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner });
8584
+ closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner });
8461
8585
  throw error;
8462
8586
  } finally {
8463
8587
  cleanBytes(chatSeed, chatPubKey);
@@ -8484,9 +8608,10 @@ var init_session = __esm(() => {
8484
8608
  });
8485
8609
 
8486
8610
  // ../../shared/community.js
8487
- var COMMUNITY_RULES_VERSION = "2026-05-11.1", COMMUNITY_SECTIONS;
8611
+ var COMMUNITY_RULES_VERSION = "2026-05-11.1", COMMUNITY_SUPPORT_EMAIL, COMMUNITY_SECTIONS;
8488
8612
  var init_community = __esm(() => {
8489
8613
  init_links();
8614
+ COMMUNITY_SUPPORT_EMAIL = links.contact.replace("mailto:", "");
8490
8615
  COMMUNITY_SECTIONS = [
8491
8616
  {
8492
8617
  title: "Use veyl directly and lawfully",
@@ -8526,7 +8651,7 @@ var init_community = __esm(() => {
8526
8651
  {
8527
8652
  title: "Reporting and support",
8528
8653
  body: [
8529
- `If someone is abusing veyl, contact us at ${links.contact.replace("mailto:", "")} and include the username, profile details, screenshots, or any evidence you choose to provide from your own device.`,
8654
+ `If someone is abusing veyl, contact us at ${COMMUNITY_SUPPORT_EMAIL} and include the username, profile details, screenshots, or any evidence you choose to provide from your own device.`,
8530
8655
  "Because veyl uses end-to-end encryption, we may rely on reports, account-level signals, and evidence you submit when reviewing abuse."
8531
8656
  ]
8532
8657
  }
@@ -106243,18 +106368,13 @@ var init_values = __esm(() => {
106243
106368
 
106244
106369
  // ../../shared/avatar.js
106245
106370
  function readAvatarVersion(value) {
106246
- if (value == null || value === "" || typeof value !== "number" && typeof value !== "string") {
106247
- return null;
106248
- }
106249
- const version = Number(value);
106250
- return Number.isSafeInteger(version) && version >= 0 ? version : null;
106251
- }
106252
- function avatarUrlWithVersion(url, version) {
106253
- if (!url)
106254
- return null;
106255
- return version == null ? url : `${url}${url.includes("?") ? "&" : "?"}v=${encodeURIComponent(String(version))}`;
106371
+ const version = typeof value === "string" ? value.trim().toLowerCase() : "";
106372
+ return AVATAR_VERSION_RE.test(version) ? version : null;
106256
106373
  }
106257
- var init_avatar = () => {};
106374
+ var AVATAR_VERSION_RE;
106375
+ var init_avatar = __esm(() => {
106376
+ AVATAR_VERSION_RE = /^[0-9a-f]{64}$/;
106377
+ });
106258
106378
 
106259
106379
  // ../../shared/moderation.js
106260
106380
  function banUntilMs(ban) {
@@ -106532,11 +106652,16 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106532
106652
  markDiag(diag, "user.avatar.fetch.start", { force: !!force, persist: !!persist, hasVersion: avatarVersion != null });
106533
106653
  try {
106534
106654
  const promise = (async () => {
106535
- const nextAvatar = await cloud.peer.avatar.url(uid, { version: avatarVersion });
106536
- if (persist && avatarVersion != null) {
106537
- cloud.peer.avatar.read(uid).then((bytes) => writeCachedAvatar(uid, { version: avatarVersion, bytes })).catch(() => {});
106655
+ if (persist && avatarVersion != null && avatarCache && typeof cloud.peer.avatar.read === "function") {
106656
+ try {
106657
+ const bytes = await cloud.peer.avatar.read(uid, { version: avatarVersion });
106658
+ const cachedAvatar = await writeCachedAvatar(uid, { version: avatarVersion, bytes });
106659
+ if (cachedAvatar) {
106660
+ return cachedAvatar;
106661
+ }
106662
+ } catch {}
106538
106663
  }
106539
- return nextAvatar;
106664
+ return cloud.peer.avatar.url(uid, { version: avatarVersion });
106540
106665
  })().then((nextAvatar) => {
106541
106666
  markDone(diag, "user.avatar.fetch", startedAt, { found: !!nextAvatar, hasVersion: avatarVersion != null });
106542
106667
  if (!nextAvatar || avatarFetch.uid !== uid || avatarFetch.key !== key) {
@@ -106876,10 +107001,12 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106876
107001
  }
106877
107002
  function refetchAvatar(options = {}) {
106878
107003
  const requestedVersion = readAvatarVersion(options?.version);
106879
- const optimistic = options?.optimistic === true;
106880
- const nextVersion = requestedVersion ?? (optimistic && state.avatarVersion != null ? state.avatarVersion + 1 : state.avatarVersion);
106881
- const persist = options?.persist ?? !(optimistic && requestedVersion == null);
106882
- return fetchAvatar(state.uid, { force: true, persist, version: nextVersion });
107004
+ const nextVersion = requestedVersion ?? state.avatarVersion;
107005
+ return fetchAvatar(state.uid, {
107006
+ force: options?.force === true || state.avatarVersion !== nextVersion || !state.avatar,
107007
+ persist: options?.persist !== false,
107008
+ version: nextVersion
107009
+ });
106883
107010
  }
106884
107011
  function setNetwork(nextNetwork) {
106885
107012
  if (activeNetwork === nextNetwork)
@@ -107147,7 +107274,7 @@ function getSubtleCrypto() {
107147
107274
  async function importAesKey(key, usages) {
107148
107275
  return getSubtleCrypto().importKey("raw", toBytes32(key, "file key"), { name: "AES-GCM" }, false, usages);
107149
107276
  }
107150
- async function sealFile(_pair, key, bytes, scope) {
107277
+ async function sealFile(key, bytes, scope) {
107151
107278
  const fileKey = new Uint8Array(toBytes32(key, "file key"));
107152
107279
  try {
107153
107280
  const cryptoKey = await importAesKey(fileKey, ["encrypt"]);
@@ -107310,20 +107437,22 @@ async function toUploadBytes(data) {
107310
107437
  }
107311
107438
  return toBytes(data, "upload bytes");
107312
107439
  }
107313
- async function makeChatFileUploadPayload(pair, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
107314
- const chatId = cleanMediaChatId(pair?.chatId);
107440
+ async function makeChatFileUploadPayload(chatId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
107441
+ const nextChatId = cleanMediaChatId(chatId);
107315
107442
  const mediaId = makeChatMediaId();
107316
- const path = mediaFilePath(chatId, mediaId);
107443
+ const path = mediaFilePath(nextChatId, mediaId);
107317
107444
  const expiresAt = Date.now() + CHAT_MEDIA_TTL_MS2;
107318
107445
  const key = createFileKey();
107319
107446
  try {
107320
107447
  const uploadBytes = await toUploadBytes(data);
107321
107448
  assertChatUploadByteSize(uploadBytes);
107449
+ const body = await sealFile(key, uploadBytes, path);
107450
+ assertChatUploadByteSize(body);
107322
107451
  return {
107323
- chatId,
107452
+ chatId: nextChatId,
107324
107453
  mediaId,
107325
107454
  path,
107326
- body: await sealFile(pair, key, uploadBytes, path),
107455
+ body,
107327
107456
  metadata: {
107328
107457
  contentType: "application/octet-stream",
107329
107458
  cacheControl
@@ -107352,10 +107481,12 @@ async function makeSharedFileUploadPayload(data, { contentType = "application/oc
107352
107481
  try {
107353
107482
  const uploadBytes = await toUploadBytes(data);
107354
107483
  assertChatUploadByteSize(uploadBytes);
107484
+ const body = await sealFile(key, uploadBytes, path);
107485
+ assertChatUploadByteSize(body);
107355
107486
  return {
107356
107487
  sharedId,
107357
107488
  path,
107358
- body: await sealFile(null, key, uploadBytes, path),
107489
+ body,
107359
107490
  metadata: {
107360
107491
  contentType,
107361
107492
  cacheControl
@@ -109281,10 +109412,19 @@ function sortedUniqueValues(items) {
109281
109412
  }
109282
109413
 
109283
109414
  // ../../shared/chat/chats.js
109415
+ function sameSigningKeys(left, right) {
109416
+ if (left === right)
109417
+ return true;
109418
+ const leftKeys = Object.keys(left || {});
109419
+ const rightKeys = Object.keys(right || {});
109420
+ if (leftKeys.length !== rightKeys.length)
109421
+ return false;
109422
+ return leftKeys.every((key) => left[key] === right[key]);
109423
+ }
109284
109424
  function sameChatShape(a, b) {
109285
109425
  if (!a || !b)
109286
109426
  return a === b;
109287
- return a.id === b.id && a.protocol === b.protocol && a.linkId === b.linkId && a.ts === b.ts && a.startMs === b.startMs && a.unseen === b.unseen && a.settings?.retention === b.settings?.retention && sameChatPreview(a.preview, b.preview);
109427
+ return a.id === b.id && a.protocol === b.protocol && a.linkId === b.linkId && a.entryId === b.entryId && a.peerChatPK === b.peerChatPK && a.peerUid === b.peerUid && sameSigningKeys(a.signingKeysByChatKey, b.signingKeysByChatKey) && a.signerShared === b.signerShared && a.ts === b.ts && a.startMs === b.startMs && a.unseen === b.unseen && a.settings?.retention === b.settings?.retention && sameChatPreview(a.preview, b.preview);
109288
109428
  }
109289
109429
  function sameChats(prev, next) {
109290
109430
  if (prev.length !== next.length)
@@ -109643,8 +109783,10 @@ function emptyPayload() {
109643
109783
  chatsById: {},
109644
109784
  profilesByUid: {},
109645
109785
  mediaByKey: {},
109786
+ walletBalance: null,
109646
109787
  resumeRoute: null,
109647
- lastCameraFacing: null
109788
+ lastCameraFacing: null,
109789
+ cloaked: false
109648
109790
  };
109649
109791
  }
109650
109792
  function isObject3(value) {
@@ -109705,8 +109847,26 @@ function normalizePayload(value) {
109705
109847
  chatsById: isObject3(input.chatsById) ? input.chatsById : {},
109706
109848
  profilesByUid: isObject3(input.profilesByUid) ? input.profilesByUid : {},
109707
109849
  mediaByKey: isObject3(input.mediaByKey) ? input.mediaByKey : {},
109850
+ walletBalance: cleanWalletBalance(input.walletBalance),
109708
109851
  resumeRoute: cleanResumeRoute(input.resumeRoute),
109709
- lastCameraFacing: cleanCameraFacing(input.lastCameraFacing)
109852
+ lastCameraFacing: cleanCameraFacing(input.lastCameraFacing),
109853
+ cloaked: input.cloaked === true
109854
+ };
109855
+ }
109856
+ function cleanWalletBalance(value) {
109857
+ if (!isObject3(value))
109858
+ return null;
109859
+ const walletPK = cleanText(value.walletPK).toLowerCase();
109860
+ const network = cleanText(value.network).toLowerCase();
109861
+ const balance = Number(value.balance);
109862
+ if (!walletPK || !network || !Number.isSafeInteger(balance) || balance < 0) {
109863
+ return null;
109864
+ }
109865
+ return {
109866
+ walletPK,
109867
+ network,
109868
+ balance,
109869
+ savedAt: Number.isFinite(value.savedAt) ? value.savedAt : 0
109710
109870
  };
109711
109871
  }
109712
109872
  function cleanResumeRoute(route) {
@@ -110152,8 +110312,8 @@ function stateFromPayload(payload, { chatId, selfChatPublicKey, peerChatPublicKe
110152
110312
  if (!cleanOlder.length && !cleanLive.length) {
110153
110313
  return { rejectReason: "empty-message-cache" };
110154
110314
  }
110155
- const historyStartReached = payload.historyStartReached === true && payload.truncated !== true;
110156
- const payloadHasOlder = historyStartReached ? false : payload.hasOlder === true || payload.truncated === true;
110315
+ const historyStartReached = payload.historyStartReached === true;
110316
+ const payloadHasOlder = payload.hasOlder === true || payload.truncated === true;
110157
110317
  const partialOlderThan = partial ? markerFromMessage(cleanOlder[0]) : null;
110158
110318
  return {
110159
110319
  historyMessages: cleanOlder,
@@ -110298,7 +110458,7 @@ function writeCachedMessageState(cache, { stateVersion = null, chatId, selfChatP
110298
110458
  signingKeysVersion: cleanText(signingKeysVersion),
110299
110459
  savedAt: Date.now(),
110300
110460
  hasOlder: nextHasOlder === true || compacted.truncated,
110301
- historyStartReached: nextHistoryStartReached === true && compacted.truncated !== true,
110461
+ historyStartReached: nextHistoryStartReached === true,
110302
110462
  olderThan: serializeMarker(storedOlderThan),
110303
110463
  olderLoaded: nextOlderLoaded === true || compacted.historyMessages.length > 0,
110304
110464
  latestServerPage: batchFromLive(compacted.latestMessages, expiredKeys, deletedKeys, nextServerBatch),
@@ -110557,6 +110717,36 @@ var init_chats2 = __esm(() => {
110557
110717
  "use client";
110558
110718
  });
110559
110719
 
110720
+ // ../../shared/cache/localdata/balance.js
110721
+ function normalizedBalance(value) {
110722
+ const balance = Number(value);
110723
+ return Number.isSafeInteger(balance) && balance >= 0 ? balance : null;
110724
+ }
110725
+ function writeCachedWalletBalance(cache, { walletPK = null, network = null, balance = null } = {}) {
110726
+ const nextWalletPK = lowerText(walletPK);
110727
+ const nextNetwork = lowerText(network);
110728
+ const nextBalance = normalizedBalance(balance);
110729
+ if (!cache || !nextWalletPK || !nextNetwork || nextBalance == null) {
110730
+ return Promise.resolve(false);
110731
+ }
110732
+ const current = cache.read?.()?.walletBalance;
110733
+ if (sameText(current?.walletPK, nextWalletPK) && sameText(current?.network, nextNetwork) && normalizedBalance(current?.balance) === nextBalance) {
110734
+ return Promise.resolve(false);
110735
+ }
110736
+ return cache.patch((draft) => {
110737
+ draft.walletBalance = {
110738
+ walletPK: nextWalletPK,
110739
+ network: nextNetwork,
110740
+ balance: nextBalance,
110741
+ savedAt: Date.now()
110742
+ };
110743
+ return draft;
110744
+ }).then(() => true);
110745
+ }
110746
+ var init_balance = __esm(() => {
110747
+ "use client";
110748
+ });
110749
+
110560
110750
  // ../../shared/cache/localdata/profiles.js
110561
110751
  function isUsableProfile(profile, now = Date.now()) {
110562
110752
  if (!profile?.uid || !profile.walletPK && !profile.chatPK) {
@@ -110797,24 +110987,26 @@ async function readCachedTransferHeadState(cache, { walletPK = null } = {}) {
110797
110987
  const stored = cache ? await cache.readPart(TX_HEAD_PART).catch(() => null) : null;
110798
110988
  return stateFromPayload2(stored, { walletPK, source: "head" });
110799
110989
  }
110800
- function writeCachedTransferState(cache, { transfers, walletPK = null, serverEndReached = false, nextOffset = null, oldestTxMs = null } = {}) {
110801
- if (!cache || !Array.isArray(transfers)) {
110802
- return Promise.resolve(false);
110990
+ function cachedTransferPayloads({ transfers, walletPK = null, serverEndReached = false, historyCount = null, nextOffset = null, oldestTxMs = null } = {}) {
110991
+ if (!Array.isArray(transfers)) {
110992
+ return null;
110803
110993
  }
110804
110994
  const nextWalletPK = lowerText(walletPK);
110805
110995
  if (!nextWalletPK) {
110806
- return Promise.resolve(false);
110996
+ return null;
110807
110997
  }
110808
110998
  const visible = [...transfers].filter((tx) => tx?.id && isVisibleTransfer(tx) && transferBelongsToWallet(tx, nextWalletPK)).sort((a, b) => txCreatedMs(b) - txCreatedMs(a));
110809
110999
  const sorted2 = visible.slice(0, Math.max(1, WALLET_TRANSFER_CACHE_LIMIT)).map(jsonClean);
110810
111000
  const capped = sorted2.length < visible.length;
111001
+ const knownHistoryCount = Math.max(sorted2.length, Math.floor(Number(historyCount) || 0));
111002
+ const complete = serverEndReached === true && !capped && sorted2.length >= knownHistoryCount;
110811
111003
  const fullPayload = {
110812
111004
  version: CACHE_VERSION,
110813
111005
  walletPK: nextWalletPK,
110814
- serverEndReached: serverEndReached === true && !capped,
110815
- historyCount: sorted2.length,
111006
+ serverEndReached: complete,
111007
+ historyCount: knownHistoryCount,
110816
111008
  nextOffset: Number.isFinite(nextOffset) ? nextOffset : sorted2.length,
110817
- oldestTxMs: serverEndReached === true && !capped && Number.isFinite(oldestTxMs) ? oldestTxMs : null,
111009
+ oldestTxMs: complete && Number.isFinite(oldestTxMs) ? oldestTxMs : null,
110818
111010
  savedAt: Date.now(),
110819
111011
  transfers: sorted2
110820
111012
  };
@@ -110823,7 +111015,24 @@ function writeCachedTransferState(cache, { transfers, walletPK = null, serverEnd
110823
111015
  ...fullPayload,
110824
111016
  transfers: headTransfers
110825
111017
  };
110826
- return Promise.all([cache.writePart(TX_HEAD_PART, headPayload), cache.writePart(TX_PART, fullPayload)]).then((results) => results.some(Boolean));
111018
+ return { fullPayload, headPayload };
111019
+ }
111020
+ function writeCachedTransferHeadState(cache, state = {}) {
111021
+ if (!cache) {
111022
+ return Promise.resolve(false);
111023
+ }
111024
+ const payloads = cachedTransferPayloads(state);
111025
+ return payloads ? cache.writePart(TX_HEAD_PART, payloads.headPayload) : Promise.resolve(false);
111026
+ }
111027
+ function writeCachedTransferState(cache, state = {}) {
111028
+ if (!cache) {
111029
+ return Promise.resolve(false);
111030
+ }
111031
+ const payloads = cachedTransferPayloads(state);
111032
+ if (!payloads) {
111033
+ return Promise.resolve(false);
111034
+ }
111035
+ return Promise.all([cache.writePart(TX_HEAD_PART, payloads.headPayload), cache.writePart(TX_PART, payloads.fullPayload)]).then((results) => results.some(Boolean));
110827
111036
  }
110828
111037
  var TX_PART = "wallet-transfers", TX_HEAD_PART = "wallet-transfers-head", CACHE_VERSION = 2, CACHE_HEAD_LIMIT;
110829
111038
  var init_transfers = __esm(() => {
@@ -110847,8 +111056,14 @@ function mediaAad(uid, network, id) {
110847
111056
  function partAad(uid, network, part) {
110848
111057
  return encoder.encode(JSON.stringify(["veyl-local-cache-part", CACHE_PART_ENVELOPE_VERSION, String(uid || ""), String(network || ""), String(part || "")]));
110849
111058
  }
111059
+ function encodePayloadData(payload) {
111060
+ return encoder.encode(JSON.stringify({ ...normalizePayload(payload), savedAt: Date.now() }));
111061
+ }
111062
+ function decodePayloadData(raw) {
111063
+ return JSON.parse(decoder.decode(toBytes(raw, "cache payload")));
111064
+ }
110850
111065
  async function sealPayload(key, payload, uid, network) {
110851
- const body = encoder.encode(JSON.stringify({ ...normalizePayload(payload), savedAt: Date.now() }));
111066
+ const body = encodePayloadData(payload);
110852
111067
  try {
110853
111068
  const { iv, ct } = await sealAes(key, body, cacheAad(uid, network));
110854
111069
  return JSON.stringify({
@@ -110870,7 +111085,7 @@ async function openPayloadData(key, raw, uid, network) {
110870
111085
  }
110871
111086
  const plain = await openAes(key, fromHexBytes(envelope.iv, "cache iv"), fromHexBytes(envelope.ct, "cache ciphertext"), cacheAad(uid, network));
110872
111087
  try {
110873
- return JSON.parse(decoder.decode(plain));
111088
+ return decodePayloadData(plain);
110874
111089
  } finally {
110875
111090
  cleanBytes(plain);
110876
111091
  }
@@ -110931,6 +111146,14 @@ function mediaCrypto(uid, network, id, keyBytes) {
110931
111146
  ivBytes: AES_IV_BYTES
110932
111147
  };
110933
111148
  }
111149
+ function payloadCrypto(uid, network, keyBytes) {
111150
+ return {
111151
+ key: keyBytes,
111152
+ aad: cacheAad(uid, network),
111153
+ version: LOCAL_DATA_CACHE_VERSION,
111154
+ ivBytes: AES_IV_BYTES
111155
+ };
111156
+ }
110934
111157
  var MEDIA_ENVELOPE_VERSION = 1, CACHE_PART_ENVELOPE_VERSION = 1;
110935
111158
  var init_crypto = __esm(() => {
110936
111159
  init_aes2();
@@ -110966,11 +111189,32 @@ async function openVaultCache({ key, storage, uid, network }) {
110966
111189
  let writeTimer = null;
110967
111190
  let writeTask = Promise.resolve();
110968
111191
  const partWriteTasks = new Map;
111192
+ let storedPayload = null;
110969
111193
  try {
110970
- const rawPayload = await openPayloadData(cacheKey, await storage.read(), uid, network);
110971
- payload = normalizePayload(rawPayload);
111194
+ storedPayload = await storage.read();
111195
+ if (!storedPayload) {
111196
+ payload = emptyPayload();
111197
+ } else if (typeof storage.openPayload === "function") {
111198
+ const readKey = new Uint8Array(cacheKey);
111199
+ let plain;
111200
+ try {
111201
+ plain = await storage.openPayload(storedPayload, payloadCrypto(uid, network, readKey));
111202
+ payload = normalizePayload(decodePayloadData(plain));
111203
+ } finally {
111204
+ cleanBytes(plain, readKey);
111205
+ }
111206
+ } else {
111207
+ payload = normalizePayload(await openPayloadData(cacheKey, storedPayload, uid, network));
111208
+ }
110972
111209
  } catch {
110973
111210
  payload = emptyPayload();
111211
+ if (storedPayload) {
111212
+ await Promise.all([
111213
+ storage.remove().catch(() => {}),
111214
+ storage.removeAllMedia().catch(() => {}),
111215
+ storage.removeAllParts().catch(() => {})
111216
+ ]);
111217
+ }
110974
111218
  }
110975
111219
  const writeNow = () => {
110976
111220
  if (closed) {
@@ -110988,10 +111232,12 @@ async function openVaultCache({ key, storage, uid, network }) {
110988
111232
  return;
110989
111233
  }
110990
111234
  const writeKey = new Uint8Array(cacheKey);
111235
+ let body;
110991
111236
  try {
110992
- await storage.write(await sealPayload(writeKey, payload, uid, network));
111237
+ const raw = typeof storage.sealPayload === "function" ? await storage.sealPayload(body = encodePayloadData(payload), payloadCrypto(uid, network, writeKey)) : await sealPayload(writeKey, payload, uid, network);
111238
+ await storage.write(raw);
110993
111239
  } finally {
110994
- cleanBytes(writeKey);
111240
+ cleanBytes(body, writeKey);
110995
111241
  }
110996
111242
  }).catch(() => {});
110997
111243
  return writeTask;
@@ -111262,6 +111508,7 @@ var init_vault2 = __esm(() => {
111262
111508
  // ../../shared/cache/localdata.js
111263
111509
  var init_localdata = __esm(() => {
111264
111510
  init_chats2();
111511
+ init_balance();
111265
111512
  init_media();
111266
111513
  init_messages2();
111267
111514
  init_profiles();
@@ -111339,7 +111586,8 @@ function makeOwnChatEntry(pair, fields = {}) {
111339
111586
  preview: fields.preview || null,
111340
111587
  saved: fields.saved || null,
111341
111588
  readMs: Number.isFinite(fields.readMs) ? fields.readMs : null,
111342
- startMs: Number.isFinite(fields.startMs) ? fields.startMs : null
111589
+ startMs: Number.isFinite(fields.startMs) ? fields.startMs : null,
111590
+ signerShared: fields.signerShared === true
111343
111591
  };
111344
111592
  }
111345
111593
  var CHAT_ENTRY_VERSION = 1;
@@ -111794,9 +112042,9 @@ function setErrorStage(error, stage, extra = {}) {
111794
112042
  Object.assign(error, extra);
111795
112043
  return error;
111796
112044
  }
111797
- async function makeChatFileUpload(pair, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
112045
+ async function makeChatFileUpload(chatId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
111798
112046
  try {
111799
- return await makeChatFileUploadPayload(pair, cid, data, {
112047
+ return await makeChatFileUploadPayload(chatId, cid, data, {
111800
112048
  cacheControl
111801
112049
  });
111802
112050
  } catch (error) {
@@ -111806,8 +112054,8 @@ async function makeChatFileUpload(pair, cid, data, { cacheControl = "private, ma
111806
112054
  });
111807
112055
  }
111808
112056
  }
111809
- async function putChatFile(pair, cid, data, options) {
111810
- const upload = await makeChatFileUpload(pair, cid, data, options);
112057
+ async function putChatFile(chatId, cid, data, options) {
112058
+ const upload = await makeChatFileUpload(chatId, cid, data, options);
111811
112059
  try {
111812
112060
  if (typeof options?.uploadChatMedia !== "function") {
111813
112061
  throw new Error("chat media upload required");
@@ -111842,7 +112090,7 @@ async function putSharedFile(data, options = {}) {
111842
112090
  throw setErrorStage(error, "upload", { path: upload.path });
111843
112091
  }
111844
112092
  }
111845
- async function readChatFile(readChatMedia, _pair, file) {
112093
+ async function readChatFile(readChatMedia, file) {
111846
112094
  let body;
111847
112095
  try {
111848
112096
  if (typeof readChatMedia !== "function") {
@@ -111900,8 +112148,8 @@ function buildAttachmentMsg(type, file) {
111900
112148
  return makeAttachment(type, file);
111901
112149
  }
111902
112150
  }
111903
- async function putAttachment(pair, cid, type, data, meta = {}) {
111904
- const file = await putChatFile(pair, cid, data, meta);
112151
+ async function putAttachment(chatId, cid, type, data, meta = {}) {
112152
+ const file = await putChatFile(chatId, cid, data, meta);
111905
112153
  return buildAttachmentMsg(type, {
111906
112154
  ...file,
111907
112155
  ...pickAttachmentMeta(meta)
@@ -111914,26 +112162,26 @@ async function putSharedAttachment(type, data, meta = {}) {
111914
112162
  ...pickAttachmentMeta(meta)
111915
112163
  });
111916
112164
  }
111917
- async function putImg(pair, cid, data, meta = {}) {
111918
- return putAttachment(pair, cid, "img", data, meta);
112165
+ async function putImg(chatId, cid, data, meta = {}) {
112166
+ return putAttachment(chatId, cid, "img", data, meta);
111919
112167
  }
111920
- async function putGif(pair, cid, data, meta = {}) {
111921
- return putAttachment(pair, cid, "gif", data, meta);
112168
+ async function putGif(chatId, cid, data, meta = {}) {
112169
+ return putAttachment(chatId, cid, "gif", data, meta);
111922
112170
  }
111923
- async function putM4a(pair, cid, data, meta = {}) {
111924
- return putAttachment(pair, cid, "m4a", data, meta);
112171
+ async function putM4a(chatId, cid, data, meta = {}) {
112172
+ return putAttachment(chatId, cid, "m4a", data, meta);
111925
112173
  }
111926
- async function putMp4(pair, cid, data, meta = {}) {
111927
- return putAttachment(pair, cid, "mp4", data, meta);
112174
+ async function putMp4(chatId, cid, data, meta = {}) {
112175
+ return putAttachment(chatId, cid, "mp4", data, meta);
111928
112176
  }
111929
- async function putFile(pair, cid, data, meta = {}) {
111930
- return putAttachment(pair, cid, "file", data, meta);
112177
+ async function putFile(chatId, cid, data, meta = {}) {
112178
+ return putAttachment(chatId, cid, "file", data, meta);
111931
112179
  }
111932
- function readMsgAttachment(readChatMedia, pair, msg) {
111933
- return readChatFile(readChatMedia, pair, msg);
112180
+ function readMsgAttachment(readChatMedia, msg) {
112181
+ return readChatFile(readChatMedia, msg);
111934
112182
  }
111935
- function readMsgFile(readChatMedia, pair, msg) {
111936
- return readMsgAttachment(readChatMedia, pair, msg);
112183
+ function readMsgFile(readChatMedia, msg) {
112184
+ return readMsgAttachment(readChatMedia, msg);
111937
112185
  }
111938
112186
  var init_media2 = __esm(() => {
111939
112187
  init_files2();
@@ -112104,7 +112352,25 @@ async function setChatRead(cloud, uid, chatPrivKey, chatId, readMs, options = {}
112104
112352
  body: await sealOwnChatEntry(chatPrivKey, entryId, {
112105
112353
  ...entry,
112106
112354
  readMs: Math.max(currentReadMs, nextReadMs),
112107
- ...hasPreviewPatch ? { preview: preview2 || null } : {}
112355
+ ...hasPreviewPatch ? { preview: preview2 || null } : {},
112356
+ ...options.signerShared === true ? { signerShared: true } : {}
112357
+ })
112358
+ });
112359
+ return true;
112360
+ }
112361
+ async function setChatSignerShared(cloud, uid, chatPrivKey, chatId) {
112362
+ if (!cloud || !uid || !chatPrivKey || !chatId) {
112363
+ return false;
112364
+ }
112365
+ const entryId = ownChatEntryId(chatPrivKey, chatId);
112366
+ const entry = await readOwnEntry(cloud, uid, chatPrivKey, entryId);
112367
+ if (!entry?.chatId || entry.signerShared === true) {
112368
+ return entry?.signerShared === true;
112369
+ }
112370
+ await cloud.user.chats.write(uid, entryId, {
112371
+ body: await sealOwnChatEntry(chatPrivKey, entryId, {
112372
+ ...entry,
112373
+ signerShared: true
112108
112374
  })
112109
112375
  });
112110
112376
  return true;
@@ -112147,7 +112413,8 @@ async function ownEntryWrite(cloud, uid, chatPrivKey, pair, fields = {}) {
112147
112413
  preview: fields.preview || existing?.preview,
112148
112414
  saved: existing?.saved || null,
112149
112415
  readMs: existing?.readMs,
112150
- startMs: existing?.startMs
112416
+ startMs: existing?.startMs,
112417
+ signerShared: fields.signerShared === true || existing?.signerShared === true
112151
112418
  });
112152
112419
  const tsMs = timestampMs(fields.ts, null);
112153
112420
  return {
@@ -112182,14 +112449,6 @@ function ownerEditedPreview(senderPubkey, message) {
112182
112449
  failed: false
112183
112450
  };
112184
112451
  }
112185
- function messageFileChatId(message) {
112186
- try {
112187
- const ref = getMediaFileRef(message?.p);
112188
- return ref?.type === "chat" ? ref.chatId : "";
112189
- } catch {
112190
- return "";
112191
- }
112192
- }
112193
112452
  async function recipientForSend(cloud, receiverChatPK, options, needed) {
112194
112453
  if (!needed) {
112195
112454
  return null;
@@ -112210,50 +112469,63 @@ async function sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, messa
112210
112469
  if (!chatId) {
112211
112470
  throw new Error("chat id required");
112212
112471
  }
112213
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112214
- if (options?.linkId && cleanText(options.linkId) !== pair.linkId) {
112215
- throw new Error("link mismatch");
112216
- }
112217
- const retention = cleanChatRetention(options?.retention ?? options?.ttlMode);
112218
- const tsMs = Date.now();
112219
- const messagePayload = withMessageRetention(message, retention);
112220
- const actionOp = actionOpForPayload(messagePayload);
112221
- const { head, body } = await sealMsg(pair, messagePayload, { op: actionOp, ts: tsMs });
112222
- const ttlMs = actionOp === CHAT_ACTION_OPS.CREATE ? newStorageTtlMs() : null;
112223
- const messageId = head.cid;
112224
- const msgData = {
112225
- head,
112226
- body,
112227
- ttlMs
112228
- };
112229
- const recipientProfile = await recipientForSend(cloud, receiverChatPK, options, updatePreview || options?.ping === true);
112230
- const sentMessage = ownerPreview(senderPubkey, messagePayload, messageId, head, tsMs, ttlMs);
112231
- const preview2 = updatePreview ? sentMessage : null;
112232
- const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112233
- peerUid: recipientProfile?.uid || cleanText(options?.receiverUid),
112234
- peerSigningPublicKey: recipientProfile?.signingPublicKey,
112235
- entry: options?.ownEntry,
112236
- settings: options?.chatSettings,
112237
- preview: preview2,
112238
- ts: tsMs
112239
- }) : null;
112240
- const pingKind = updatePreview ? "message" : cleanText(options?.pingKind) || "ping";
112241
- const ping = recipientProfile?.uid && (updatePreview || options?.ping === true) ? await sealPing(senderPubkey, senderPrivkey, receiverChatPK, {
112242
- kind: pingKind,
112243
- chatId,
112244
- senderUid: cleanText(options?.senderUid),
112245
- messageId,
112246
- ts: tsMs
112247
- }) : null;
112248
- const write = await cloud.chat.messages.send({
112249
- chatId,
112250
- messageId,
112251
- message: msgData,
112252
- ownerEntry,
112253
- inbox: ping ? { recipientUid: recipientProfile?.uid, ping } : null,
112254
- onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
112472
+ return withCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId }, async (pair) => {
112473
+ if (options?.linkId && cleanText(options.linkId) !== pair.linkId) {
112474
+ throw new Error("link mismatch");
112475
+ }
112476
+ const retention = cleanChatRetention(options?.retention ?? options?.ttlMode);
112477
+ const tsMs = Date.now();
112478
+ const messagePayload = withMessageRetention(message, retention);
112479
+ const actionOp = actionOpForPayload(messagePayload);
112480
+ const { head, body } = await sealMsg(pair, messagePayload, { op: actionOp, ts: tsMs });
112481
+ const ttlMs = actionOp === CHAT_ACTION_OPS.CREATE ? newStorageTtlMs() : null;
112482
+ const messageId = head.cid;
112483
+ const msgData = {
112484
+ head,
112485
+ body,
112486
+ ttlMs
112487
+ };
112488
+ const recipientProfile = await recipientForSend(cloud, receiverChatPK, options, updatePreview || options?.ping === true);
112489
+ const sentMessage = ownerPreview(senderPubkey, messagePayload, messageId, head, tsMs, ttlMs);
112490
+ const preview2 = updatePreview ? sentMessage : null;
112491
+ const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112492
+ peerUid: recipientProfile?.uid || cleanText(options?.receiverUid),
112493
+ peerSigningPublicKey: recipientProfile?.signingPublicKey,
112494
+ entry: options?.ownEntry,
112495
+ settings: options?.chatSettings,
112496
+ preview: preview2,
112497
+ ts: tsMs
112498
+ }) : null;
112499
+ const pingKind = updatePreview ? "message" : cleanText(options?.pingKind) || "ping";
112500
+ const ping = recipientProfile?.uid && (updatePreview || options?.ping === true) ? await sealPing(senderPubkey, senderPrivkey, receiverChatPK, {
112501
+ kind: pingKind,
112502
+ chatId,
112503
+ senderUid: cleanText(options?.senderUid),
112504
+ messageId,
112505
+ ts: tsMs
112506
+ }) : null;
112507
+ const write = await cloud.chat.messages.send({
112508
+ chatId,
112509
+ messageId,
112510
+ message: msgData,
112511
+ ownerEntry,
112512
+ inbox: ping ? { recipientUid: recipientProfile?.uid, ping, notify: options?.notify !== false } : null,
112513
+ onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
112514
+ });
112515
+ const delivered = options?.ping === true ? !!ping && write?.delivered !== false : write?.delivered !== false;
112516
+ if (ping && delivered && ownerEntry && options?.ownEntry?.signerShared !== true) {
112517
+ await setChatSignerShared(cloud, cleanText(options?.senderUid), senderPrivkey, chatId).catch(() => false);
112518
+ }
112519
+ return {
112520
+ chatId,
112521
+ msgId: messageId,
112522
+ cid: head.cid,
112523
+ message: sentMessage,
112524
+ preview: preview2,
112525
+ delivered,
112526
+ ...write?.skipped ? { skipped: true } : {}
112527
+ };
112255
112528
  });
112256
- return { chatId, msgId: messageId, cid: head.cid, message: sentMessage, preview: preview2, ...write?.skipped ? { skipped: true } : {} };
112257
112529
  }
112258
112530
  async function sendReadReceipt(cloud, senderPubkey, senderPrivkey, receiverChatPK, target, options = {}) {
112259
112531
  const receipt = {
@@ -112261,7 +112533,13 @@ async function sendReadReceipt(cloud, senderPubkey, senderPrivkey, receiverChatP
112261
112533
  cid: makeCid(),
112262
112534
  s: senderPubkey
112263
112535
  };
112264
- return sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, receipt, { ...options, updatePreview: false, ping: false });
112536
+ return sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, receipt, {
112537
+ ...options,
112538
+ updatePreview: false,
112539
+ ping: options?.ping === true,
112540
+ pingKind: "read",
112541
+ notify: false
112542
+ });
112265
112543
  }
112266
112544
  async function sendReaction(cloud, senderPubkey, senderPrivkey, receiverChatPK, target, emoji, options = {}) {
112267
112545
  const reaction = {
@@ -112363,27 +112641,28 @@ async function setChatRetention(cloud, chatId, senderPubkey, senderPrivkey, peer
112363
112641
  throw new Error("vault locked");
112364
112642
  }
112365
112643
  const nextRetention = cleanChatRetention(retention);
112366
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
112367
- if (chatId && pair.chatId !== chatId) {
112368
- throw new Error("chat mismatch");
112369
- }
112370
- const systemMessage = {
112371
- ...makeRetentionSystemMsg(nextRetention),
112372
- cid: cleanText(options?.cid) || makeCid(),
112373
- s: senderPubkey
112374
- };
112375
- await sendMsg(cloud, senderPubkey, senderPrivkey, peerChatPK, systemMessage, {
112376
- chatId,
112377
- linkId: pair.linkId,
112378
- updatePreview: true,
112379
- retention: nextRetention,
112380
- chatExists: true,
112381
- senderUid: options?.senderUid,
112382
- ownEntry: options?.ownEntry,
112383
- chatSettings: { retention: nextRetention },
112384
- onCommitted: options?.onCommitted
112644
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
112645
+ if (chatId && pair.chatId !== chatId) {
112646
+ throw new Error("chat mismatch");
112647
+ }
112648
+ const systemMessage = {
112649
+ ...makeRetentionSystemMsg(nextRetention),
112650
+ cid: cleanText(options?.cid) || makeCid(),
112651
+ s: senderPubkey
112652
+ };
112653
+ await sendMsg(cloud, senderPubkey, senderPrivkey, peerChatPK, systemMessage, {
112654
+ chatId,
112655
+ linkId: pair.linkId,
112656
+ updatePreview: true,
112657
+ retention: nextRetention,
112658
+ chatExists: true,
112659
+ senderUid: options?.senderUid,
112660
+ ownEntry: options?.ownEntry,
112661
+ chatSettings: { retention: nextRetention },
112662
+ onCommitted: options?.onCommitted
112663
+ });
112664
+ return nextRetention;
112385
112665
  });
112386
- return nextRetention;
112387
112666
  }
112388
112667
  async function uploadAttachmentMsg(_cloud, senderPubkey, senderPrivkey, receiverChatPK, attachment = {}) {
112389
112668
  if (!senderPrivkey || !senderPubkey) {
@@ -112397,95 +112676,96 @@ async function uploadAttachmentMsg(_cloud, senderPubkey, senderPrivkey, receiver
112397
112676
  if (!chatId) {
112398
112677
  throw new Error("chat id required");
112399
112678
  }
112400
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112401
112679
  const type = cleanText(attachment?.type) || "file";
112402
112680
  const data = attachment?.data;
112403
112681
  const meta = attachment?.meta || {};
112404
112682
  switch (type) {
112405
112683
  case "img":
112406
- return putImg(pair, nextCid, data, meta);
112684
+ return putImg(chatId, nextCid, data, meta);
112407
112685
  case "gif":
112408
- return putGif(pair, nextCid, data, meta);
112686
+ return putGif(chatId, nextCid, data, meta);
112409
112687
  case "m4a":
112410
- return putM4a(pair, nextCid, data, meta);
112688
+ return putM4a(chatId, nextCid, data, meta);
112411
112689
  case "mp4":
112412
- return putMp4(pair, nextCid, data, meta);
112690
+ return putMp4(chatId, nextCid, data, meta);
112413
112691
  case "file":
112414
- return putFile(pair, nextCid, data, meta);
112692
+ return putFile(chatId, nextCid, data, meta);
112415
112693
  default:
112416
- return putAttachment(pair, nextCid, type, data, meta);
112694
+ return putAttachment(chatId, nextCid, type, data, meta);
112417
112695
  }
112418
112696
  }
112419
112697
  async function updateMsg(cloud, chatId, msgId, senderPubkey, senderPrivkey, receiverChatPK, newMessage, options = {}) {
112420
112698
  if (!senderPubkey || !senderPrivkey)
112421
112699
  throw new Error("vault locked");
112422
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112423
- if (pair.chatId !== chatId) {
112424
- throw new Error("chat mismatch");
112425
- }
112426
- const target = cleanText(newMessage?.cid) || cleanText(msgId);
112427
- if (!target) {
112428
- throw new Error("message target required");
112429
- }
112430
- const op = newMessage?.t === "req" && cleanText(newMessage?.tx) ? CHAT_ACTION_OPS.PAY_CONFIRM : CHAT_ACTION_OPS.EDIT;
112431
- const tsMs = Date.now();
112432
- const action = {
112433
- ...newMessage || {},
112434
- cid: makeCid(),
112435
- s: senderPubkey
112436
- };
112437
- const { head, body } = await sealMsg(pair, action, { op, target, ts: tsMs });
112438
- const messageId = head.cid;
112439
- const updatePreview = options?.updatePreview === true;
112440
- const preview2 = updatePreview ? ownerEditedPreview(senderPubkey, newMessage) : null;
112441
- const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112442
- peerUid: cleanText(options?.receiverUid),
112443
- peerSigningPublicKey: cleanText(options?.peerSigningPublicKey),
112444
- entry: options?.ownEntry,
112445
- preview: preview2,
112446
- ts: preview2?.ts ?? tsMs
112447
- }) : null;
112448
- await cloud.chat.messages.send({
112449
- chatId,
112450
- messageId,
112451
- message: {
112452
- head,
112453
- body,
112454
- ttlMs: null
112455
- },
112456
- ownerEntry,
112457
- inbox: null
112700
+ return withCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId }, async (pair) => {
112701
+ if (pair.chatId !== chatId) {
112702
+ throw new Error("chat mismatch");
112703
+ }
112704
+ const target = cleanText(newMessage?.cid) || cleanText(msgId);
112705
+ if (!target) {
112706
+ throw new Error("message target required");
112707
+ }
112708
+ const op = newMessage?.t === "req" && cleanText(newMessage?.tx) ? CHAT_ACTION_OPS.PAY_CONFIRM : CHAT_ACTION_OPS.EDIT;
112709
+ const tsMs = Date.now();
112710
+ const action = {
112711
+ ...newMessage || {},
112712
+ cid: makeCid(),
112713
+ s: senderPubkey
112714
+ };
112715
+ const { head, body } = await sealMsg(pair, action, { op, target, ts: tsMs });
112716
+ const messageId = head.cid;
112717
+ const updatePreview = options?.updatePreview === true;
112718
+ const preview2 = updatePreview ? ownerEditedPreview(senderPubkey, newMessage) : null;
112719
+ const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112720
+ peerUid: cleanText(options?.receiverUid),
112721
+ peerSigningPublicKey: cleanText(options?.peerSigningPublicKey),
112722
+ entry: options?.ownEntry,
112723
+ preview: preview2,
112724
+ ts: preview2?.ts ?? tsMs
112725
+ }) : null;
112726
+ await cloud.chat.messages.send({
112727
+ chatId,
112728
+ messageId,
112729
+ message: {
112730
+ head,
112731
+ body,
112732
+ ttlMs: null
112733
+ },
112734
+ ownerEntry,
112735
+ inbox: null
112736
+ });
112737
+ return {
112738
+ chatId,
112739
+ msgId: messageId,
112740
+ cid: head.cid,
112741
+ preview: preview2
112742
+ };
112458
112743
  });
112459
- return {
112460
- chatId,
112461
- msgId: messageId,
112462
- cid: head.cid,
112463
- preview: preview2
112464
- };
112465
112744
  }
112466
112745
  async function deleteMsg(cloud, chatId, messageOrId, senderPubkey, senderPrivkey, peerChatPK, options = {}) {
112467
112746
  if (!cloud || !chatId || !messageOrId || !senderPubkey || !senderPrivkey || !peerChatPK) {
112468
112747
  return false;
112469
112748
  }
112470
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
112471
- if (pair.chatId !== chatId) {
112472
- return false;
112473
- }
112474
- const item = messageDeleteItems([messageOrId])[0];
112475
- const target = cleanText(options?.docId) || cleanText(item?.id);
112476
- if (!target || target.startsWith("local:")) {
112477
- return false;
112478
- }
112479
- const deleteTarget = cleanText(options?.target) || cleanText(getMessageKey(messageOrId)) || target;
112480
- await sendDeleteAction(cloud, senderPubkey, senderPrivkey, peerChatPK, deleteTarget, {
112481
- chatId,
112482
- linkId: options?.linkId,
112483
- senderUid: options?.senderUid
112484
- });
112485
- await cloud.chat.messages.delete(chatId, target, {
112486
- mediaPaths: item?.mediaPath ? [item.mediaPath] : []
112749
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
112750
+ if (pair.chatId !== chatId) {
112751
+ return false;
112752
+ }
112753
+ const item = messageDeleteItems([messageOrId])[0];
112754
+ const target = cleanText(options?.docId) || cleanText(item?.id);
112755
+ if (!target || target.startsWith("local:")) {
112756
+ return false;
112757
+ }
112758
+ const deleteTarget = cleanText(options?.target) || cleanText(getMessageKey(messageOrId)) || target;
112759
+ await sendDeleteAction(cloud, senderPubkey, senderPrivkey, peerChatPK, deleteTarget, {
112760
+ chatId,
112761
+ linkId: options?.linkId,
112762
+ senderUid: options?.senderUid
112763
+ });
112764
+ await cloud.chat.messages.delete(chatId, target, {
112765
+ mediaPaths: item?.mediaPath ? [item.mediaPath] : []
112766
+ });
112767
+ return true;
112487
112768
  });
112488
- return true;
112489
112769
  }
112490
112770
  async function deleteMsgs(cloud, chatId, messages, senderPubkey, senderPrivkey, peerChatPK) {
112491
112771
  if (!cloud || !chatId || !senderPubkey || !senderPrivkey || !peerChatPK) {
@@ -112495,31 +112775,31 @@ async function deleteMsgs(cloud, chatId, messages, senderPubkey, senderPrivkey,
112495
112775
  if (!items.length) {
112496
112776
  return 0;
112497
112777
  }
112498
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
112499
- if (pair.chatId !== chatId) {
112500
- return 0;
112501
- }
112502
- let deleted = 0;
112503
- for (let index = 0;index < items.length; index += DELETE_WRITE_BATCH_SIZE) {
112504
- const chunk = items.slice(index, index + DELETE_WRITE_BATCH_SIZE);
112505
- const targets = chunk.map((item) => cleanText(item.id)).filter((target) => target && !target.startsWith("local:"));
112506
- const mediaPaths = chunk.map((item) => cleanText(item.mediaPath)).filter(Boolean);
112507
- deleted += await cloud.chat.messages.deleteMany(chatId, targets, { mediaPaths });
112508
- }
112509
- return deleted;
112778
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
112779
+ if (pair.chatId !== chatId) {
112780
+ return 0;
112781
+ }
112782
+ let deleted = 0;
112783
+ for (let index = 0;index < items.length; index += DELETE_WRITE_BATCH_SIZE) {
112784
+ const chunk = items.slice(index, index + DELETE_WRITE_BATCH_SIZE);
112785
+ const targets = chunk.map((item) => cleanText(item.id)).filter((target) => target && !target.startsWith("local:"));
112786
+ const mediaPaths = chunk.map((item) => cleanText(item.mediaPath)).filter(Boolean);
112787
+ deleted += await cloud.chat.messages.deleteMany(chatId, targets, { mediaPaths });
112788
+ }
112789
+ return deleted;
112790
+ });
112510
112791
  }
112511
- async function readMsgMedia(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg) {
112512
- return readMsgAttachment2(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg);
112792
+ async function readMsgMedia(readChatMedia, msg) {
112793
+ return readMsgAttachment2(readChatMedia, msg);
112513
112794
  }
112514
- async function readMsgAttachment2(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg) {
112795
+ async function readMsgAttachment2(readChatMedia, msg) {
112515
112796
  if (typeof readChatMedia !== "function") {
112516
112797
  throw new Error("chat media read required");
112517
112798
  }
112518
- if (!userChatPK || !userPrivKey || !peerChatPK || !msg) {
112799
+ if (!msg) {
112519
112800
  return null;
112520
112801
  }
112521
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: msg?.chatId || messageFileChatId(msg) });
112522
- return readMsgFile(readChatMedia, pair, msg);
112802
+ return readMsgFile(readChatMedia, msg);
112523
112803
  }
112524
112804
  var DELETE_WRITE_BATCH_SIZE;
112525
112805
  var init_write = __esm(() => {
@@ -112762,14 +113042,14 @@ function hasInvalidStoredMediaRef(message) {
112762
113042
  return true;
112763
113043
  }
112764
113044
  }
112765
- function readMessageMedia(cloud, media, chatPK, chatPrivateKey, peerChatPK, message) {
113045
+ function readMessageMedia(cloud, media, message) {
112766
113046
  const readChatMedia = cloud?.chat?.media?.read;
112767
113047
  if (typeof media?.readMessageFile === "function") {
112768
- return media.readMessageFile(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
113048
+ return media.readMessageFile(readChatMedia, message);
112769
113049
  }
112770
- return readMsgMedia(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
113050
+ return readMsgMedia(readChatMedia, message);
112771
113051
  }
112772
- function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey, localCache }) {
113052
+ function createChatSave({ cloud, media = {}, chatBanned, localCache }) {
112773
113053
  const makeMessagePermanent = async (chatId, message) => {
112774
113054
  if (chatBanned) {
112775
113055
  throw makeChatUnavailableError();
@@ -112787,7 +113067,7 @@ function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey,
112787
113067
  const updated = await makeMsgTemporary(cloud, chatId, list, ttl);
112788
113068
  return { updated, ttl };
112789
113069
  };
112790
- const readMessageFile = async (peerChatPK, message) => {
113070
+ const readMessageFile = async (_peerChatPK, message) => {
112791
113071
  if (chatBanned) {
112792
113072
  throw makeChatUnavailableError();
112793
113073
  }
@@ -112809,7 +113089,7 @@ function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey,
112809
113089
  throw makeFileGoneError();
112810
113090
  }
112811
113091
  try {
112812
- const bytes = await readMessageMedia(cloud, media, chatPK, chatPrivateKey, peerChatPK, message);
113092
+ const bytes = await readMessageMedia(cloud, media, message);
112813
113093
  saveMedia(localCache, message, bytes, message);
112814
113094
  return bytes;
112815
113095
  } catch (error) {
@@ -112876,7 +113156,7 @@ function messageExpiryGroups(messages, selfChatPublicKey, seenAt) {
112876
113156
  { messages: day, expiresAt: seenAt + AFTER_SEEN_MS }
112877
113157
  ].filter((group) => group.messages.length);
112878
113158
  }
112879
- function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localCache, pendingReadRef, readCacheRef, readWriteInterval, getChatRetention, setChats, diag }) {
113159
+ function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localCache, pendingReadRef, readCacheRef, readWriteInterval, getChatRetention, sendOptionsForPeer, setChats, diag }) {
112880
113160
  const scheduleRead = (chatId, message, previewMs, sendReceipt, expiryMessages = []) => {
112881
113161
  scheduleReadWrite({
112882
113162
  pendingRead: pendingReadRef.current,
@@ -112887,24 +113167,37 @@ function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localC
112887
113167
  expiryMessages: sendReceipt ? expiryMessages : [],
112888
113168
  interval: readWriteInterval,
112889
113169
  write: async (pending) => {
113170
+ let receiptError = null;
113171
+ let receiptResult = null;
113172
+ let introduceSigner = false;
113173
+ if (pending.receipt) {
113174
+ const sendOptions = sendOptionsForPeer(pending.receipt.peerChatPK);
113175
+ introduceSigner = sendOptions?.signerShared !== true;
113176
+ try {
113177
+ receiptResult = await sendReadReceipt(cloud, chatPK, chatPrivateKey, pending.receipt.peerChatPK, pending.receipt.target, {
113178
+ chatId,
113179
+ retention: getChatRetention(chatId),
113180
+ senderUid: uid,
113181
+ receiverUid: sendOptions?.receiverUid,
113182
+ ping: introduceSigner
113183
+ });
113184
+ } catch (error) {
113185
+ receiptError = error;
113186
+ }
113187
+ }
112890
113188
  const writes = [
112891
113189
  setChatRead(cloud, uid, chatPrivateKey, chatId, pending.previewMs, {
112892
- preview: (currentPreview) => withChatPreviewOpened(currentPreview, pending.message, pending.previewMs, chatPK) || currentPreview
113190
+ preview: (currentPreview) => withChatPreviewOpened(currentPreview, pending.message, pending.previewMs, chatPK) || currentPreview,
113191
+ signerShared: introduceSigner && receiptResult?.delivered === true
112893
113192
  })
112894
113193
  ];
112895
113194
  if (pending.receipt) {
112896
- writes.push(sendReadReceipt(cloud, chatPK, chatPrivateKey, pending.receipt.peerChatPK, pending.receipt.target, {
112897
- chatId,
112898
- retention: getChatRetention(chatId),
112899
- senderUid: uid
112900
- }));
112901
113195
  for (const group of messageExpiryGroups(pending.expiryMessages, chatPK, pending.expirySeenAt)) {
112902
113196
  writes.push(setMessageExpiry(cloud, chatId, group.messages, group.expiresAt));
112903
113197
  }
112904
113198
  }
112905
113199
  const results = await Promise.allSettled(writes);
112906
113200
  const ownerError = results[0]?.status === "rejected" ? results[0].reason : null;
112907
- const receiptError = pending.receipt && results[1]?.status === "rejected" ? results[1].reason : null;
112908
113201
  if (ownerError) {
112909
113202
  markError(diag, "chat.read.state.write", Date.now(), ownerError);
112910
113203
  }
@@ -112912,7 +113205,7 @@ function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localC
112912
113205
  readCacheRef.current.delete(chatId);
112913
113206
  markError(diag, "chat.read.receipt.write", Date.now(), receiptError);
112914
113207
  }
112915
- for (const result of results.slice(pending.receipt ? 2 : 1)) {
113208
+ for (const result of results.slice(1)) {
112916
113209
  if (result?.status === "rejected") {
112917
113210
  markError(diag, "chat.message.expiry.write", Date.now(), result.reason);
112918
113211
  }
@@ -113455,8 +113748,11 @@ function withSharedMediaUpload(cloud, media, meta = {}) {
113455
113748
  };
113456
113749
  }
113457
113750
  function uploadMessageAttachment(cloud, media, senderPubkey, senderPrivkey, receiverChatPK, attachment = {}) {
113751
+ if (!senderPubkey || !senderPrivkey) {
113752
+ throw new Error("vault locked");
113753
+ }
113458
113754
  if (typeof media?.uploadAttachment === "function") {
113459
- return media.uploadAttachment(senderPubkey, senderPrivkey, receiverChatPK, attachment);
113755
+ return media.uploadAttachment(attachment);
113460
113756
  }
113461
113757
  return uploadAttachmentMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, withMediaUpload(cloud, media, attachment));
113462
113758
  }
@@ -113754,17 +114050,21 @@ ${cid}` : "";
113754
114050
  const requiredPreview = previewRequired || sendOptions.chatExists !== true;
113755
114051
  let local = await showLocalMessage(peerChatPK, message, sendOptions);
113756
114052
  let committed = false;
114053
+ let sent = false;
114054
+ const markSent = () => {
114055
+ if (sent)
114056
+ return;
114057
+ sent = true;
114058
+ sentChatIdsRef.current.add(local.chatId);
114059
+ markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
114060
+ };
113757
114061
  const markCommitted = () => {
113758
114062
  if (committed)
113759
114063
  return;
113760
114064
  committed = true;
113761
- sentChatIdsRef.current.add(local.chatId);
114065
+ markSent();
113762
114066
  markDone(diag, "chat.send.commit", startedAt, { type: local?.t || "txt", previewRequired: !!requiredPreview });
113763
114067
  };
113764
- const markSent = () => {
113765
- sentChatIdsRef.current.add(local.chatId);
113766
- markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
113767
- };
113768
114068
  return await new Promise((resolve, reject) => {
113769
114069
  const job = {
113770
114070
  previewKey: local.chatId,
@@ -113982,7 +114282,8 @@ ${cid}` : "";
113982
114282
  linkId: local.linkId,
113983
114283
  version: local.version,
113984
114284
  sendOptions,
113985
- nextAttachment: prepared.nextAttachment
114285
+ nextAttachment: prepared.nextAttachment,
114286
+ committed: false
113986
114287
  };
113987
114288
  }));
113988
114289
  return await new Promise((resolve, reject) => {
@@ -113991,7 +114292,9 @@ ${cid}` : "";
113991
114292
  reject,
113992
114293
  onError: () => {
113993
114294
  for (const item of locals) {
113994
- markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114295
+ if (!item.committed) {
114296
+ markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114297
+ }
113995
114298
  }
113996
114299
  },
113997
114300
  run: async () => {
@@ -114006,6 +114309,7 @@ ${cid}` : "";
114006
114309
  results.push({ peerChatPK: item.peerChatPK, ok: false, error: uploadError });
114007
114310
  continue;
114008
114311
  }
114312
+ let sentMessage;
114009
114313
  try {
114010
114314
  let uploaded = uploads.get(uploadKey);
114011
114315
  if (!uploaded) {
@@ -114018,17 +114322,32 @@ ${cid}` : "";
114018
114322
  cid: item.cid,
114019
114323
  s: chatPK
114020
114324
  };
114021
- const sentMessage = withMessageRetention(sent, item.sendOptions.retention);
114325
+ sentMessage = withMessageRetention(sent, item.sendOptions.retention);
114022
114326
  rememberCachedLocalMedia(item.chatId, item.cid, sentMessage);
114023
- await sendMsg(cloud, chatPK, chatPrivateKey, item.peerChatPK, sentMessage, { ...item.sendOptions, chatId: item.chatId, linkId: item.linkId, senderUid: uid });
114024
- markLocalStatus(item.chatId, item.cid, LOCAL_SENT);
114327
+ await sendMsg(cloud, chatPK, chatPrivateKey, item.peerChatPK, sentMessage, {
114328
+ ...item.sendOptions,
114329
+ chatId: item.chatId,
114330
+ linkId: item.linkId,
114331
+ senderUid: uid,
114332
+ onCommitted: () => {
114333
+ if (item.committed)
114334
+ return;
114335
+ item.committed = true;
114336
+ sentChatIdsRef.current.add(item.chatId);
114337
+ markLocalStatus(item.chatId, item.cid, LOCAL_SENT);
114338
+ }
114339
+ });
114025
114340
  results.push({ peerChatPK: item.peerChatPK, ok: true, message: sentMessage });
114026
114341
  } catch (error) {
114027
114342
  if (!uploads.has(uploadKey)) {
114028
114343
  uploadErrors.set(uploadKey, error);
114029
114344
  }
114030
- markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114031
- results.push({ peerChatPK: item.peerChatPK, ok: false, error });
114345
+ if (item.committed) {
114346
+ results.push({ peerChatPK: item.peerChatPK, ok: true, message: sentMessage });
114347
+ } else {
114348
+ markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114349
+ results.push({ peerChatPK: item.peerChatPK, ok: false, error });
114350
+ }
114032
114351
  }
114033
114352
  }
114034
114353
  return results;
@@ -114835,7 +115154,6 @@ async function decryptRecordEntries(records, userChatPK, userPrivKey, peerChatPK
114835
115154
  return [];
114836
115155
  }
114837
115156
  const signingKeysVersion = signingKeysFingerprint(options?.signingKeysByChatKey);
114838
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId });
114839
115157
  const entryOptions = { ...options, signingKeysVersion };
114840
115158
  const shouldYield = positiveInt(options?.yieldEvery, 0) > 0;
114841
115159
  const entries = new Array(source.length).fill(null);
@@ -114852,38 +115170,40 @@ async function decryptRecordEntries(records, userChatPK, userPrivKey, peerChatPK
114852
115170
  }
114853
115171
  pending.push({ record, index });
114854
115172
  };
114855
- const openPending = async (pending) => {
114856
- if (!pending.length) {
114857
- return;
114858
- }
114859
- const opened = await decryptRecordBatchWithPair(pending.map((item) => item.record), pair, cache, entryOptions);
114860
- for (let index = 0;index < opened.length; index += 1) {
114861
- const entry = opened[index];
114862
- if (entry) {
114863
- entries[pending[index].index] = entry;
115173
+ return withCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId }, async (pair) => {
115174
+ const openPending = async (pending) => {
115175
+ if (!pending.length) {
115176
+ return;
115177
+ }
115178
+ const opened = await decryptRecordBatchWithPair(pending.map((item) => item.record), pair, cache, entryOptions);
115179
+ for (let index = 0;index < opened.length; index += 1) {
115180
+ const entry = opened[index];
115181
+ if (entry) {
115182
+ entries[pending[index].index] = entry;
115183
+ }
114864
115184
  }
115185
+ };
115186
+ if (!options?.signal && !shouldYield) {
115187
+ const pending = [];
115188
+ for (let index = 0;index < source.length; index += 1) {
115189
+ collectRecord(source[index], index, pending);
115190
+ }
115191
+ await openPending(pending);
115192
+ return entries.filter(Boolean);
114865
115193
  }
114866
- };
114867
- if (!options?.signal && !shouldYield) {
114868
- const pending = [];
114869
- for (let index = 0;index < source.length; index += 1) {
114870
- collectRecord(source[index], index, pending);
115194
+ const chunkSize = positiveInt(options?.yieldEvery, source.length || 1);
115195
+ for (let index = 0;index < source.length; index += chunkSize) {
115196
+ throwIfAborted(options?.signal);
115197
+ const chunk = source.slice(index, index + chunkSize);
115198
+ const pending = [];
115199
+ for (let offset = 0;offset < chunk.length; offset += 1) {
115200
+ collectRecord(chunk[offset], index + offset, pending);
115201
+ }
115202
+ await openPending(pending);
115203
+ await maybeYield(index + chunk.length, options);
114871
115204
  }
114872
- await openPending(pending);
114873
115205
  return entries.filter(Boolean);
114874
- }
114875
- const chunkSize = positiveInt(options?.yieldEvery, source.length || 1);
114876
- for (let index = 0;index < source.length; index += chunkSize) {
114877
- throwIfAborted(options?.signal);
114878
- const chunk = source.slice(index, index + chunkSize);
114879
- const pending = [];
114880
- for (let offset = 0;offset < chunk.length; offset += 1) {
114881
- collectRecord(chunk[offset], index + offset, pending);
114882
- }
114883
- await openPending(pending);
114884
- await maybeYield(index + chunk.length, options);
114885
- }
114886
- return entries.filter(Boolean);
115206
+ });
114887
115207
  }
114888
115208
  function messageQueryLimit(pageSize) {
114889
115209
  return positiveInt(pageSize, MSG_BATCH_SIZE);
@@ -114936,7 +115256,11 @@ function readableEntryWindow(entries, pageSize, chatPK, peerChatPK, options = {}
114936
115256
  const limitCount = positiveInt(pageSize, MSG_BATCH_SIZE);
114937
115257
  const projectedEntries = readableProjectedEntries(source, chatPK, peerChatPK, options);
114938
115258
  if (!projectedEntries.length) {
114939
- return { count: 0, entries: [], start: 0 };
115259
+ return {
115260
+ count: 0,
115261
+ entries: source.filter((entry) => isProjectionSupportMsg(entry?.message)),
115262
+ start: 0
115263
+ };
114940
115264
  }
114941
115265
  const indexes = projectedEntries.map((entry) => entry.index);
114942
115266
  const projectedByIndex = new Map(projectedEntries.map((entry) => [entry.index, entry.message]));
@@ -115229,9 +115553,10 @@ async function decryptMsg(msgData, userChatPK, userPrivKey, peerChatPK, options
115229
115553
  return null;
115230
115554
  }
115231
115555
  try {
115232
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId });
115233
- const message = await openMsg(pair, msgData, { signingKeysByChatKey: options?.signingKeysByChatKey, crypto: options?.crypto });
115234
- return normalizeDecryptedMsg(msgData, message);
115556
+ return await withCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId }, async (pair) => {
115557
+ const message = await openMsg(pair, msgData, { signingKeysByChatKey: options?.signingKeysByChatKey, crypto: options?.crypto });
115558
+ return normalizeDecryptedMsg(msgData, message);
115559
+ });
115235
115560
  } catch {
115236
115561
  return null;
115237
115562
  }
@@ -116301,12 +116626,12 @@ function warmKeyStore(ref) {
116301
116626
  }
116302
116627
  return ref.current;
116303
116628
  }
116304
- function readMessageMedia2(cloud, media, chatPK, chatPrivateKey, peerChatPK, message) {
116629
+ function readMessageMedia2(cloud, media, message) {
116305
116630
  const readChatMedia = cloud?.chat?.media?.read;
116306
116631
  if (typeof media?.readMessageFile === "function") {
116307
- return media.readMessageFile(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
116632
+ return media.readMessageFile(readChatMedia, message);
116308
116633
  }
116309
- return readMsgMedia(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
116634
+ return readMsgMedia(readChatMedia, message);
116310
116635
  }
116311
116636
  function isDenied3(error) {
116312
116637
  return error?.code === "permission-denied";
@@ -116513,12 +116838,12 @@ function createChatMessageBatches({ cloud, media = {}, chatPK, chatPrivateKey, c
116513
116838
  return;
116514
116839
  }
116515
116840
  const tasks = buildMediaTasks();
116516
- const readFile = async (peerChatPK, message) => {
116841
+ const readFile = async (_peerChatPK, message) => {
116517
116842
  const cached = await readCachedMedia(localCache, message);
116518
116843
  if (cached?.byteLength) {
116519
116844
  return cached;
116520
116845
  }
116521
- const bytes = await readMessageMedia2(cloud, media, chatPK, chatPrivateKey, peerChatPK, message);
116846
+ const bytes = await readMessageMedia2(cloud, media, message);
116522
116847
  if (bytes?.byteLength) {
116523
116848
  saveMedia(localCache, message, bytes, message);
116524
116849
  }
@@ -117265,6 +117590,16 @@ function pingSigningKeys(ping, existing) {
117265
117590
  [ping.payload.senderChatPK]: ping.payload.signingPublicKey
117266
117591
  };
117267
117592
  }
117593
+ function chatSettingsFromPreview(settings, preview2) {
117594
+ const current = normalizeChatSettings(settings);
117595
+ if (!isSystemMsg(preview2)) {
117596
+ return current;
117597
+ }
117598
+ return {
117599
+ ...current,
117600
+ retention: cleanChatRetention(preview2.retention)
117601
+ };
117602
+ }
117268
117603
  function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options = {}) {
117269
117604
  const ts = timestampMs(ms, null) ?? timestampMs(existing?.ts, null) ?? timestampMs(preview2?.ts, null) ?? 0;
117270
117605
  if (!ts) {
@@ -117281,10 +117616,11 @@ function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options
117281
117616
  peerChatPK: ping.payload.senderChatPK,
117282
117617
  peerUid: existing?.peerUid || cleanText(ping.payload.senderUid) || null,
117283
117618
  signingKeysByChatKey: pingSigningKeys(ping, existing),
117284
- settings: existing?.settings,
117619
+ settings: chatSettingsFromPreview(existing?.settings, preview2),
117285
117620
  saved: existing?.saved || null,
117286
117621
  readMs,
117287
117622
  startMs,
117623
+ signerShared: existing?.signerShared === true,
117288
117624
  preview: visiblePreview,
117289
117625
  ts,
117290
117626
  unseen: visiblePreview ? isChatUnseenForUser({ preview: visiblePreview, readMs }, userChatPK) : false
@@ -117340,10 +117676,11 @@ async function savePing(cloud, uid, userChatPK, userPrivKey, ping, options = {})
117340
117676
  peerUid: peerUid2 || existing?.peerUid,
117341
117677
  peerSigningPublicKey: ping.payload.signingPublicKey || existing?.signingKeysByChatKey?.[ping.payload.senderChatPK],
117342
117678
  signingKeysByChatKey,
117343
- settings: existing?.settings,
117679
+ settings: chatSettingsFromPreview(existing?.settings, preview2),
117344
117680
  preview: preview2 || existing?.preview,
117345
117681
  readMs: existing?.readMs,
117346
- startMs: existing ? existing.startMs : timestampMs(options.startPreview?.ts ?? preview2?.ts, null, { positive: true })
117682
+ startMs: existing ? existing.startMs : timestampMs(options.startPreview?.ts ?? preview2?.ts, null, { positive: true }),
117683
+ signerShared: existing?.signerShared === true
117347
117684
  });
117348
117685
  const body = await sealOwnChatEntry(userPrivKey, entryId, entry);
117349
117686
  const record = { body };
@@ -117353,14 +117690,16 @@ async function savePing(cloud, uid, userChatPK, userPrivKey, ping, options = {})
117353
117690
  await cloud.user.chats.write(uid, entryId, record);
117354
117691
  return true;
117355
117692
  }
117356
- function chatWithReadPreview(existing, preview2, userChatPK) {
117357
- if (!existing?.id || !preview2) {
117693
+ function chatWithReadPreview(existing, preview2, userChatPK, fields = {}) {
117694
+ if (!existing?.id) {
117358
117695
  return null;
117359
117696
  }
117360
117697
  return {
117361
117698
  ...existing,
117362
- preview: preview2,
117363
- unseen: isChatUnseenForUser({ preview: preview2, readMs: existing.readMs }, userChatPK)
117699
+ signingKeysByChatKey: fields.signingKeysByChatKey || existing.signingKeysByChatKey,
117700
+ peerUid: fields.peerUid || existing.peerUid,
117701
+ preview: preview2 || existing.preview,
117702
+ unseen: preview2 ? isChatUnseenForUser({ preview: preview2, readMs: existing.readMs }, userChatPK) : existing.unseen
117364
117703
  };
117365
117704
  }
117366
117705
  function patchReadPingPreview(existing, receiptPreview, userChatPK) {
@@ -117441,6 +117780,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
117441
117780
  let current = chatsById.get(group.chatId) || null;
117442
117781
  let wrote = false;
117443
117782
  let handled = false;
117783
+ const identityPing = group.message?.ping || group.read?.ping || group.delete?.ping;
117784
+ const peerUid2 = await resolvePingUid(cloud, identityPing?.payload);
117444
117785
  if (group.message) {
117445
117786
  const item = group.message;
117446
117787
  const entryId = ownChatEntryId(userPrivKey, item.chatId);
@@ -117461,7 +117802,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
117461
117802
  signingKeysByChatKey,
117462
117803
  preview: preview2,
117463
117804
  startPreview,
117464
- tsMs: item.ms
117805
+ tsMs: item.ms,
117806
+ peerUid: peerUid2
117465
117807
  });
117466
117808
  }
117467
117809
  if (group.read) {
@@ -117471,22 +117813,24 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
117471
117813
  const signingKeysByChatKey = pingSigningKeys(item.ping, existing);
117472
117814
  const receiptPreview = await readPingMsg(cloud, userChatPK, userPrivKey, item.ping, signingKeysByChatKey);
117473
117815
  const openedPreview = patchReadPingPreview(existing, receiptPreview, userChatPK);
117474
- const chat = chatWithReadPreview(current, openedPreview, userChatPK);
117816
+ const chat = chatWithReadPreview(current || existing, openedPreview, userChatPK, {
117817
+ signingKeysByChatKey,
117818
+ peerUid: peerUid2
117819
+ });
117475
117820
  handled = true;
117476
- if (openedPreview) {
117477
- if (chat) {
117478
- current = chat;
117479
- chatsById.set(chat.id, chat);
117480
- options.onPingChat?.(chat);
117481
- }
117482
- const writeExisting = chat || existing;
117483
- wrote = await savePing(cloud, uid, userChatPK, userPrivKey, item.ping, {
117484
- existing: writeExisting,
117485
- signingKeysByChatKey,
117486
- preview: openedPreview,
117487
- writeTs: false
117488
- }) || wrote;
117821
+ if (chat) {
117822
+ current = chat;
117823
+ chatsById.set(chat.id, chat);
117824
+ options.onPingChat?.(chat);
117489
117825
  }
117826
+ const writeExisting = chat || existing;
117827
+ wrote = await savePing(cloud, uid, userChatPK, userPrivKey, item.ping, {
117828
+ existing: writeExisting,
117829
+ signingKeysByChatKey,
117830
+ preview: openedPreview || existing?.preview || null,
117831
+ writeTs: false,
117832
+ peerUid: peerUid2
117833
+ }) || wrote;
117490
117834
  }
117491
117835
  if (group.delete) {
117492
117836
  const item = group.delete;
@@ -117507,7 +117851,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
117507
117851
  wrote = await savePing(cloud, uid, userChatPK, userPrivKey, item.ping, {
117508
117852
  existing: writeExisting,
117509
117853
  signingKeysByChatKey,
117510
- preview: deletedPreview
117854
+ preview: deletedPreview,
117855
+ peerUid: peerUid2
117511
117856
  }) || wrote;
117512
117857
  }
117513
117858
  }
@@ -117529,6 +117874,7 @@ var init_inbox = __esm(() => {
117529
117874
  init_ping();
117530
117875
  init_query();
117531
117876
  init_chats();
117877
+ init_ttl();
117532
117878
  init_time();
117533
117879
  init_protocol();
117534
117880
  });
@@ -117912,6 +118258,7 @@ async function decryptChatEntry(entryRecord, userChatPK, userPrivKey) {
117912
118258
  saved: entry.saved || null,
117913
118259
  readMs,
117914
118260
  startMs,
118261
+ signerShared: entry.signerShared === true,
117915
118262
  preview: visiblePreview,
117916
118263
  ts,
117917
118264
  unseen: visiblePreview ? isChatUnseenForUser({ preview: visiblePreview, readMs }, userChatPK) : false
@@ -118024,6 +118371,7 @@ function createChatList({
118024
118371
  let chats = [];
118025
118372
  let peers = [];
118026
118373
  let isChatDataReady = false;
118374
+ let isChatListLive = false;
118027
118375
  let chatSourceReady = false;
118028
118376
  let lastChat = null;
118029
118377
  let serverChatIds = [];
@@ -118060,6 +118408,7 @@ function createChatList({
118060
118408
  chats,
118061
118409
  peers,
118062
118410
  isChatDataReady,
118411
+ isChatListLive,
118063
118412
  hasChats: chats.length > 0,
118064
118413
  previewNow,
118065
118414
  lastChat,
@@ -118093,6 +118442,13 @@ function createChatList({
118093
118442
  isChatDataReady = value;
118094
118443
  publish();
118095
118444
  };
118445
+ const setIsChatListLive = (next) => {
118446
+ const value = nextValue(isChatListLive, next);
118447
+ if (Object.is(isChatListLive, value))
118448
+ return;
118449
+ isChatListLive = value;
118450
+ publish();
118451
+ };
118096
118452
  const setLastChat = (next) => {
118097
118453
  const value = nextValue(lastChat, next);
118098
118454
  if (Object.is(lastChat, value))
@@ -118302,6 +118658,7 @@ function createChatList({
118302
118658
  const resetChatList = (ready = false) => {
118303
118659
  chatSourceReady = ready;
118304
118660
  syncChatDataReady();
118661
+ setIsChatListLive(false);
118305
118662
  setChats((prev) => prev.length ? [] : prev);
118306
118663
  setPeers((prev) => prev.length ? [] : prev);
118307
118664
  setLastChat((prev) => prev ? null : prev);
@@ -118391,6 +118748,7 @@ function createChatList({
118391
118748
  resetAllChatState();
118392
118749
  return;
118393
118750
  }
118751
+ setIsChatListLive(false);
118394
118752
  let cancelled = false;
118395
118753
  const hydrateKey = localCache?.id ? `${localCache.id}:${chatPK}` : "";
118396
118754
  if (hydrateKey && lastHydratedCacheKeyRef.current !== hydrateKey) {
@@ -118490,6 +118848,7 @@ function createChatList({
118490
118848
  writeCache: true,
118491
118849
  serverEndReached: cachedHistoryEndRef.current
118492
118850
  });
118851
+ setIsChatListLive(true);
118493
118852
  listenUpdateSeqRef.current += 1;
118494
118853
  markDiag(diag, "chat.provider.listen.update", {
118495
118854
  seq: listenUpdateSeqRef.current,
@@ -118532,6 +118891,7 @@ function createChatList({
118532
118891
  markChatSourceReady();
118533
118892
  fillVisibleChatPage();
118534
118893
  }, (error) => {
118894
+ setIsChatListLive(false);
118535
118895
  markError(diag, "chat.provider.listen", listenStartedAt, error);
118536
118896
  if (shouldResetChatListAfterListenError(error)) {
118537
118897
  resetAllChatState();
@@ -118685,6 +119045,7 @@ function createChatList({
118685
119045
  chatExists: !!peerChat?.id || hasServerChatForPeer(peerChatPK),
118686
119046
  receiverUid: peerChat?.peerUid || "",
118687
119047
  peerSigningPublicKey: peerChat?.signingKeysByChatKey?.[peerChatPK] || "",
119048
+ signerShared: peerChat?.signerShared === true,
118688
119049
  ownEntry: peerChat?.entryId ? peerChat : null
118689
119050
  };
118690
119051
  };
@@ -119382,6 +119743,7 @@ function createChatSession({
119382
119743
  readCacheRef,
119383
119744
  readWriteInterval,
119384
119745
  getChatRetention,
119746
+ sendOptionsForPeer,
119385
119747
  setChats,
119386
119748
  diag
119387
119749
  });
@@ -120526,7 +120888,7 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
120526
120888
  }
120527
120889
  if (avatarVersion != null && typeof cloud.peer.avatar.read === "function") {
120528
120890
  try {
120529
- const bytes = await cloud.peer.avatar.read(uid);
120891
+ const bytes = await cloud.peer.avatar.read(uid, { version: avatarVersion });
120530
120892
  const cachedSource = await writeCachedAvatar(uid, avatarVersion, bytes);
120531
120893
  if (cachedSource) {
120532
120894
  return cachedSource;
@@ -121063,6 +121425,10 @@ function getSatsBalance(result) {
121063
121425
  }
121064
121426
  return satsBalance;
121065
121427
  }
121428
+ function displayBalanceValue(value) {
121429
+ const balance = Number(value);
121430
+ return Number.isSafeInteger(balance) && balance >= 0 ? balance : null;
121431
+ }
121066
121432
  function samePlainObject(a, b) {
121067
121433
  if (a === b) {
121068
121434
  return true;
@@ -121091,9 +121457,10 @@ function sameTokenBalances(a, b) {
121091
121457
  }
121092
121458
  return true;
121093
121459
  }
121094
- function createWalletBalance({ wallet, diag, onChange = null }) {
121460
+ function createWalletBalance({ wallet, diag, initialDisplayBalance = null, onBalance = null, onChange = null }) {
121095
121461
  let snapshot = {
121096
121462
  balance: null,
121463
+ displayBalance: displayBalanceValue(initialDisplayBalance),
121097
121464
  satsBalance: null,
121098
121465
  tokenBalances: new Map,
121099
121466
  balanceReady: false,
@@ -121103,10 +121470,28 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121103
121470
  snapshot = { ...snapshot, ...changes };
121104
121471
  onChange?.();
121105
121472
  };
121473
+ const publishDisplayBalance = (changes, value, trusted = false) => {
121474
+ const displayBalance = displayBalanceValue(value);
121475
+ if (displayBalance == null || !trusted && !snapshot.balanceReady) {
121476
+ return null;
121477
+ }
121478
+ if (!Object.is(snapshot.displayBalance, displayBalance)) {
121479
+ changes.displayBalance = displayBalance;
121480
+ }
121481
+ return displayBalance;
121482
+ };
121106
121483
  const setBalance = (nextBalance) => {
121107
121484
  const value = typeof nextBalance === "function" ? nextBalance(snapshot.balance) : nextBalance;
121485
+ const changes = {};
121108
121486
  if (!Object.is(snapshot.balance, value)) {
121109
- publish({ balance: value });
121487
+ changes.balance = value;
121488
+ }
121489
+ const displayBalance = publishDisplayBalance(changes, value);
121490
+ if (Object.keys(changes).length) {
121491
+ publish(changes);
121492
+ }
121493
+ if (displayBalance != null) {
121494
+ onBalance?.(displayBalance);
121110
121495
  }
121111
121496
  };
121112
121497
  const setTokenBalances = (nextTokenBalances) => {
@@ -121115,12 +121500,13 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121115
121500
  publish({ tokenBalances: new Map(source) });
121116
121501
  }
121117
121502
  };
121118
- const setBalanceResult = (result) => {
121503
+ const setBalanceResult = (result, { ready = false } = {}) => {
121119
121504
  const changes = {};
121120
121505
  const nextBalance = getBalanceValue(result);
121121
121506
  if (nextBalance != null && !Object.is(snapshot.balance, nextBalance)) {
121122
121507
  changes.balance = nextBalance;
121123
121508
  }
121509
+ const nextDisplayBalance = publishDisplayBalance(changes, nextBalance, true);
121124
121510
  const nextSatsBalance = getSatsBalance(result);
121125
121511
  if (nextSatsBalance && !samePlainObject(snapshot.satsBalance, nextSatsBalance)) {
121126
121512
  changes.satsBalance = nextSatsBalance;
@@ -121129,9 +121515,15 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121129
121515
  if (nextTokenBalances && !sameTokenBalances(snapshot.tokenBalances, nextTokenBalances)) {
121130
121516
  changes.tokenBalances = new Map(nextTokenBalances);
121131
121517
  }
121518
+ if (ready && !snapshot.balanceReady) {
121519
+ changes.balanceReady = true;
121520
+ }
121132
121521
  if (Object.keys(changes).length) {
121133
121522
  publish(changes);
121134
121523
  }
121524
+ if (nextDisplayBalance != null) {
121525
+ onBalance?.(nextDisplayBalance);
121526
+ }
121135
121527
  };
121136
121528
  const setSatsBalanceResult = (nextSatsBalance) => {
121137
121529
  if (!nextSatsBalance || typeof nextSatsBalance !== "object") {
@@ -121144,9 +121536,13 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121144
121536
  if (nextSatsBalance.available != null && !Object.is(snapshot.balance, nextSatsBalance.available)) {
121145
121537
  changes.balance = nextSatsBalance.available;
121146
121538
  }
121539
+ const nextDisplayBalance = publishDisplayBalance(changes, nextSatsBalance.available);
121147
121540
  if (Object.keys(changes).length) {
121148
121541
  publish(changes);
121149
121542
  }
121543
+ if (nextDisplayBalance != null) {
121544
+ onBalance?.(nextDisplayBalance);
121545
+ }
121150
121546
  };
121151
121547
  const getBalance = async () => {
121152
121548
  if (!wallet) {
@@ -121160,7 +121556,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121160
121556
  }
121161
121557
  try {
121162
121558
  const result = await wallet.getBalance();
121163
- setBalanceResult(result);
121559
+ setBalanceResult(result, { ready: true });
121164
121560
  markDone(diag, "wallet.balance", startedAt, { hasBalance: getBalanceValue(result) != null, hasSatsBalance: !!getSatsBalance(result), hasTokenBalances: !!getTokenBalances(result) });
121165
121561
  } catch (error) {
121166
121562
  markError(diag, "wallet.balance", startedAt, error);
@@ -121180,6 +121576,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121180
121576
  const reset = () => {
121181
121577
  snapshot = {
121182
121578
  balance: null,
121579
+ displayBalance: null,
121183
121580
  satsBalance: null,
121184
121581
  tokenBalances: new Map,
121185
121582
  balanceReady: false,
@@ -121196,7 +121593,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121196
121593
  setSatsBalanceResult
121197
121594
  };
121198
121595
  }
121199
- var init_balance = () => {};
121596
+ var init_balance2 = () => {};
121200
121597
 
121201
121598
  // ../../shared/wallet/deposits.js
121202
121599
  async function getClaimableDepositUtxos(wallet, options = {}) {
@@ -122221,11 +122618,6 @@ function createWalletPrivacy({ wallet, ghostWallet, diag }) {
122221
122618
  const startedAt = Date.now();
122222
122619
  const desired = desiredPrivacy;
122223
122620
  markDiag(diag, "wallet.privacy.start", { ghostWallet: desired });
122224
- const current = typeof wallet.getWalletSettings === "function" ? await wallet.getWalletSettings() : null;
122225
- if (closed || requestRevision !== revision || current?.privateEnabled === true === desired) {
122226
- markDone(diag, "wallet.privacy", startedAt, { changed: false, cancelled: closed || requestRevision !== revision });
122227
- return;
122228
- }
122229
122621
  await wallet.setPrivacyEnabled(desired);
122230
122622
  markDone(diag, "wallet.privacy", startedAt, { changed: true });
122231
122623
  }).catch((error) => {
@@ -122979,8 +123371,8 @@ function pendingTransfers(transfers = []) {
122979
123371
  function pendingTransferIds(transfers = []) {
122980
123372
  return pendingTransfers(transfers).map((tx) => String(tx.id));
122981
123373
  }
122982
- function pendingIncomingWalletTransfers(transfers = []) {
122983
- return pendingTransfers(transfers).filter((tx) => isIncomingTransfer(tx) && isWalletTransfer(tx));
123374
+ function pendingIncomingTransfers(transfers = []) {
123375
+ return pendingTransfers(transfers).filter(isIncomingTransfer);
122984
123376
  }
122985
123377
  function uniqueIds(ids = []) {
122986
123378
  return [...new Set((Array.isArray(ids) ? ids : [ids]).map((id) => String(id || "")).filter(Boolean))];
@@ -123075,7 +123467,7 @@ function adaptiveClaimBatchSize(maxBatchSize, queueDepth) {
123075
123467
  }
123076
123468
  return 1;
123077
123469
  }
123078
- function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, transferStore, appState = null, deferBackgroundWork = null, backgroundSignal = null, diag }) {
123470
+ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, eagerCacheHistory = true, transferStore, appState = null, deferBackgroundWork = null, backgroundSignal = null, refreshBalance = null, diag }) {
123079
123471
  if (!transferStore || typeof transferStore.subscribe !== "function" || typeof transferStore.getSnapshot !== "function" || typeof transferStore.getCacheState !== "function" || typeof transferStore.setHistory !== "function") {
123080
123472
  throw new Error("createWalletTransferSession requires transferStore");
123081
123473
  }
@@ -123089,6 +123481,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123089
123481
  const initialTransfers = canUseInitialStore ? transferStore.getTransfers() : [];
123090
123482
  const initialHistoryTransfers = canUseInitialStore ? transferStore.getHistoryTransfers() : [];
123091
123483
  const initialTxReady = canUseInitialStore && (initialStoreSnapshot.ready || initialTransfers.length > 0 || initialHistoryTransfers.length > 0);
123484
+ const initialCacheReady = initialTxReady && (eagerCacheHistory === false || initialStoreSnapshot.serverEndReached === true && initialHistoryTransfers.length >= initialStoreSnapshot.historyCount);
123092
123485
  const listeners = new Set;
123093
123486
  let isTxLoading = false;
123094
123487
  let publishedSnapshot = null;
@@ -123139,7 +123532,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123139
123532
  const cacheHydrateKeyRef = { current: "" };
123140
123533
  const cacheHydratePromiseRef = { current: null };
123141
123534
  const fullCacheHydratePromiseRef = { current: null };
123142
- const cacheWriteRef = { current: { timer: null, cache: null, state: null } };
123535
+ const cacheWriteRef = { current: { timer: null, cache: null, state: null, fullHistory: false } };
123143
123536
  const cacheDirtyRef = { current: false };
123144
123537
  const lastFetchAtRef = { current: 0 };
123145
123538
  const claimBatchSize = Math.max(1, Number(pendingClaimBatchSize) || PENDING_TRANSFER_CLAIM_BATCH_SIZE);
@@ -123467,9 +123860,9 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123467
123860
  return false;
123468
123861
  }
123469
123862
  const cachedTransfers = filterTransfersForWallet(cached.transfers, walletPKRef.current);
123470
- const cachedPendingIncoming = pendingIncomingWalletTransfers(cachedTransfers);
123471
- const cachedPending = pendingTransfers(cachedTransfers);
123472
- const cachedPublishTransfers = cachedTransfers;
123863
+ const cachedPublishTransfers = !publish && historyTransfersRef.current.length ? mergeTransferPageForWallet(historyTransfersRef.current, cachedTransfers, walletPKRef.current, "append") : cachedTransfers;
123864
+ const cachedPendingIncoming = pendingIncomingTransfers(cachedPublishTransfers);
123865
+ const cachedPending = pendingTransfers(cachedPublishTransfers);
123473
123866
  rememberPendingTransferIds(cachedPending.map((tx) => tx.id));
123474
123867
  markDiag(diag, label, {
123475
123868
  elapsedMs: Date.now() - startedAt,
@@ -123608,9 +124001,11 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123608
124001
  pending.timer = null;
123609
124002
  const cache = pending.cache;
123610
124003
  const state = pending.state;
124004
+ const fullHistory = pending.fullHistory;
123611
124005
  pending.state = null;
123612
124006
  if (cache && state) {
123613
- writeCachedTransferState(cache, state);
124007
+ const write = fullHistory ? writeCachedTransferState : writeCachedTransferHeadState;
124008
+ write(cache, state);
123614
124009
  }
123615
124010
  };
123616
124011
  const queueWalletFetch = (read) => {
@@ -123715,7 +124110,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123715
124110
  return pendingQueryPromiseRef.current;
123716
124111
  };
123717
124112
  const pendingIncomingClaimCandidates = (sourceTransfers = []) => {
123718
- return pendingIncomingWalletTransfers(sourceTransfers).filter((tx) => {
124113
+ return pendingIncomingTransfers(sourceTransfers).filter((tx) => {
123719
124114
  const id = String(tx?.id || "");
123720
124115
  return id && !claimedTransferIdsRef.current.has(id);
123721
124116
  });
@@ -123931,6 +124326,9 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123931
124326
  batchClaimedIds.push(...staged);
123932
124327
  flushClaimed();
123933
124328
  const uniqueBatchClaimedIds = uniqueIds(batchClaimedIds);
124329
+ if (uniqueBatchClaimedIds.length && typeof refreshBalance === "function") {
124330
+ await refreshBalance();
124331
+ }
123934
124332
  const unclaimedIds = candidateIds.filter((id) => !uniqueBatchClaimedIds.includes(id) && !claimedTransferIdsRef.current.has(id));
123935
124333
  if (unclaimedIds.length) {
123936
124334
  const elapsedMs = Date.now() - startedAt;
@@ -124453,7 +124851,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124453
124851
  try {
124454
124852
  await yieldToUi();
124455
124853
  const pendingTransfers2 = await getPendingTransfersSnapshot();
124456
- const incomingTransfers = pendingIncomingWalletTransfers(pendingTransfers2);
124854
+ const incomingTransfers = pendingIncomingTransfers(pendingTransfers2);
124457
124855
  const claimCandidates = pendingIncomingClaimCandidates(incomingTransfers);
124458
124856
  if (!incomingTransfers.length) {
124459
124857
  markDone(diag, "wallet.pendingTxs.active", startedAt, { pending: 0, claimed: 0, reason });
@@ -124798,13 +125196,16 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124798
125196
  if (cacheWriteRef.current.timer) {
124799
125197
  clearTimeout(cacheWriteRef.current.timer);
124800
125198
  }
124801
- cacheWriteRef.current = { timer: null, cache: null, state: null };
125199
+ cacheWriteRef.current = { timer: null, cache: null, state: null, fullHistory: false };
124802
125200
  cacheDirtyRef.current = false;
124803
125201
  };
124804
125202
  const startCacheHydration = () => {
124805
125203
  if (!wallet || !localCache || !walletPKRef.current) {
124806
125204
  return;
124807
125205
  }
125206
+ if (initialCacheReady) {
125207
+ return;
125208
+ }
124808
125209
  const hydrateKey = `${localCache.id || "cache"}:${walletPKRef.current}`;
124809
125210
  if (cacheHydrateKeyRef.current === hydrateKey) {
124810
125211
  return;
@@ -124829,7 +125230,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124829
125230
  return;
124830
125231
  }
124831
125232
  const hydratedHead = applyCachedTransferState(cachedHead, { label: "wallet.provider.cache.hydrate", publish: true, startedAt: hydrateStartedAt });
124832
- if (cancelled)
125233
+ if (cancelled || !eagerCacheHistory)
124833
125234
  return;
124834
125235
  await loadFullTransferCache({ label: "wallet.provider.cache.fullHydrate", publish: !hydratedHead });
124835
125236
  }).catch((error) => {
@@ -124853,12 +125254,10 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124853
125254
  if (!cacheDirtyRef.current && !cacheWriteRef.current.timer) {
124854
125255
  return;
124855
125256
  }
124856
- if (historyTransfersRef.current.length < knownHistoryCountRef.current) {
124857
- return;
124858
- }
124859
125257
  cacheDirtyRef.current = false;
124860
125258
  cacheWriteRef.current.cache = localCache;
124861
125259
  cacheWriteRef.current.state = transferStore.getCacheState();
125260
+ cacheWriteRef.current.fullHistory = historyTransfersRef.current.length >= knownHistoryCountRef.current;
124862
125261
  if (cacheWriteRef.current.timer) {
124863
125262
  clearTimeout(cacheWriteRef.current.timer);
124864
125263
  }
@@ -124887,7 +125286,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124887
125286
  serverHistoryComplete: serverHistoryCompleteRef.current,
124888
125287
  hasMoreTxs: !historyComplete,
124889
125288
  hasPendingTxs,
124890
- hasPendingIncomingTxs: pendingIncomingWalletTransfers(transfersRef.current).length > 0,
125289
+ hasPendingIncomingTxs: pendingIncomingTransfers(transfersRef.current).length > 0,
124891
125290
  beginSdkActivity,
124892
125291
  getRecentTxs,
124893
125292
  claimPendingIncomingTransfers: claimPendingIncomingTransfers2,
@@ -125238,10 +125637,12 @@ function createWalletSession({
125238
125637
  pendingClaimBatchSize = null,
125239
125638
  activeTransferRefreshMs = null,
125240
125639
  eagerOutgoingStatusRefresh = false,
125640
+ eagerCacheHistory = true,
125241
125641
  sentTransferRefreshDelayMs = null,
125242
125642
  deferBackgroundWork = null,
125243
125643
  appState = null,
125244
125644
  transferStore,
125645
+ cachedBalance = null,
125245
125646
  ghostWallet = true,
125246
125647
  diag = null
125247
125648
  }) {
@@ -125259,6 +125660,7 @@ function createWalletSession({
125259
125660
  const txSubscriptions = createFieldSubscriptions(() => publishedSnapshot?.txValue);
125260
125661
  let polling = null;
125261
125662
  let readyDiag = null;
125663
+ let refreshWalletBalance = null;
125262
125664
  const transferSession = createWalletTransferSession({
125263
125665
  wallet,
125264
125666
  walletPK,
@@ -125266,10 +125668,12 @@ function createWalletSession({
125266
125668
  claimIncomingTransfers,
125267
125669
  pendingClaimBatchSize,
125268
125670
  eagerOutgoingStatusRefresh,
125671
+ eagerCacheHistory,
125269
125672
  transferStore,
125270
125673
  appState,
125271
125674
  deferBackgroundWork,
125272
125675
  backgroundSignal: backgroundAbort.signal,
125676
+ refreshBalance: () => refreshWalletBalance?.(),
125273
125677
  diag
125274
125678
  });
125275
125679
  const getTransferState = () => transferSession.getSnapshot();
@@ -125289,7 +125693,16 @@ function createWalletSession({
125289
125693
  listener();
125290
125694
  }
125291
125695
  };
125292
- const balance = createWalletBalance({ wallet, diag, onChange: publish });
125696
+ const balance = createWalletBalance({
125697
+ wallet,
125698
+ diag,
125699
+ initialDisplayBalance: cachedBalance,
125700
+ onBalance: (value) => {
125701
+ writeCachedWalletBalance(localCache, { walletPK, network, balance: value }).catch(() => {});
125702
+ },
125703
+ onChange: publish
125704
+ });
125705
+ refreshWalletBalance = balance.getBalance;
125293
125706
  const funding = createFundingAddress({ wallet, network, diag, onChange: publish });
125294
125707
  const updateWalletData = createWalletData({
125295
125708
  wallet,
@@ -125370,13 +125783,14 @@ function createWalletSession({
125370
125783
  wallet,
125371
125784
  network,
125372
125785
  balance: balanceState.balance,
125786
+ displayBalance: balanceState.displayBalance,
125373
125787
  satsBalance: balanceState.satsBalance,
125374
125788
  tokenBalances: balanceState.tokenBalances,
125375
125789
  fundingAddress: funding.getSnapshot(),
125376
125790
  balanceReady: balanceState.balanceReady,
125377
125791
  isBalanceLoading: balanceState.isBalanceLoading,
125378
125792
  isWalletDataReady: true,
125379
- isWalletDataLoaded: balanceState.balanceReady && currentTransferState.txReady,
125793
+ isWalletDataLoaded: balanceState.balanceReady || currentTransferState.txReady,
125380
125794
  refresh: claims.refresh,
125381
125795
  requestTransferDiscoveryHotWindow: polling.requestTransferDiscoveryHotWindow,
125382
125796
  claimDeposits: claims.claimDeposits,
@@ -125468,7 +125882,7 @@ function createWalletSession({
125468
125882
  };
125469
125883
  }
125470
125884
  var init_session4 = __esm(() => {
125471
- init_balance();
125885
+ init_balance2();
125472
125886
  init_claims();
125473
125887
  init_funding();
125474
125888
  init_lightning();
@@ -125477,6 +125891,7 @@ var init_session4 = __esm(() => {
125477
125891
  init_send2();
125478
125892
  init_transfers2();
125479
125893
  init_withdraw();
125894
+ init_localdata();
125480
125895
  });
125481
125896
 
125482
125897
  // ../../shared/wallet/transferstore.js
@@ -125685,6 +126100,7 @@ function createTransferStore() {
125685
126100
  transfers: state.historyTransfers,
125686
126101
  walletPK: state.walletPK,
125687
126102
  serverEndReached: state.serverEndReached,
126103
+ historyCount: state.historyCount,
125688
126104
  nextOffset: state.nextOffset,
125689
126105
  oldestTxMs: state.serverEndReached ? state.oldestVerifiedTxMs : null
125690
126106
  };
@@ -169168,9 +169584,13 @@ function peerRecordFromDoc(snap) {
169168
169584
  function peerRecordsFromSnapshot(snapshot) {
169169
169585
  return snapshot.docs.map(peerRecordFromDoc).filter(Boolean);
169170
169586
  }
169171
- function avatarPath(uid) {
169587
+ function avatarPath(uid, version8) {
169172
169588
  requireUid(uid);
169173
- return `${uid}/avatar.webp`;
169589
+ const avatarVersion = readAvatarVersion(version8);
169590
+ if (!avatarVersion) {
169591
+ throw new Error("avatar version required");
169592
+ }
169593
+ return `${uid}/avatars/${avatarVersion}.webp`;
169174
169594
  }
169175
169595
  function reportEvidencePath(reporter, targetUid, evidenceId) {
169176
169596
  if (!reporter || !targetUid || !evidenceId) {
@@ -169236,6 +169656,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169236
169656
  }
169237
169657
  let activeVaultSigner = null;
169238
169658
  const localSessionTerminations = new Set;
169659
+ const pendingAvatars = new Map;
169239
169660
  function pageTs(value) {
169240
169661
  if (value instanceof Timestamp2) {
169241
169662
  return value;
@@ -169318,7 +169739,12 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169318
169739
  "Content-Type": upload?.metadata?.contentType || "application/octet-stream"
169319
169740
  };
169320
169741
  if (typeof uploadSignedStorageBytes === "function") {
169321
- await uploadSignedStorageBytes(url, upload?.body, { ...signed, method, headers, metadata: upload?.metadata || {}, path: upload?.path || "" });
169742
+ await uploadSignedStorageBytes(url, upload?.body, { ...signed, method, headers, metadata: upload?.metadata || {}, path: upload?.path || "" }).catch((error) => {
169743
+ if (error?.status === 412 && (headers["X-Goog-If-Generation-Match"] === "0" || headers["x-goog-if-generation-match"] === "0")) {
169744
+ return true;
169745
+ }
169746
+ throw error;
169747
+ });
169322
169748
  return true;
169323
169749
  }
169324
169750
  const payload = typeof Blob !== "undefined" && upload?.body instanceof Blob ? upload.body : toBytes(upload?.body, "upload bytes");
@@ -169327,6 +169753,9 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169327
169753
  headers,
169328
169754
  body: payload
169329
169755
  });
169756
+ if (response.status === 412 && (headers["X-Goog-If-Generation-Match"] === "0" || headers["x-goog-if-generation-match"] === "0")) {
169757
+ return true;
169758
+ }
169330
169759
  if (!response.ok) {
169331
169760
  const error = new Error(`signed upload failed (${response.status || 0})`);
169332
169761
  error.status = response.status || 0;
@@ -169496,6 +169925,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169496
169925
  }, onError);
169497
169926
  }
169498
169927
  async function logout() {
169928
+ pendingAvatars.clear();
169499
169929
  await signOut(requireAuth());
169500
169930
  return true;
169501
169931
  }
@@ -169629,25 +170059,45 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169629
170059
  await callFunction("acceptAgreement");
169630
170060
  return true;
169631
170061
  }
169632
- async function writeProfileAvatar(uid, avatar) {
170062
+ async function setProfileAvatar(uid, data) {
169633
170063
  requireUid(uid);
169634
- await updateDoc(doc(db, "profiles", uid), { avatar });
169635
- return true;
169636
- }
169637
- async function uploadProfileAvatar(uid, data, { contentType = "image/webp" } = {}) {
169638
- const targetStorage = requireStorage2();
169639
- const path = avatarPath(uid);
169640
- const result = typeof uploadStorageBytes === "function" ? await uploadStorageBytes(targetStorage, path, data, { contentType }) : await uploadBytes(ref(targetStorage, path), typeof Blob !== "undefined" && data instanceof Blob ? data : toBytes(data, "upload bytes"), { contentType });
169641
- const url = await getDownloadURL(ref(targetStorage, path));
169642
- return {
169643
- url,
169644
- generation: result?.metadata?.generation ?? result?.generation ?? null
169645
- };
169646
- }
169647
- async function deleteProfileAvatar(uid) {
169648
- const targetStorage = requireStorage2();
169649
- await deleteObject(ref(targetStorage, avatarPath(uid)));
169650
- return true;
170064
+ if (requireAuth().currentUser?.uid !== uid) {
170065
+ throw new Error("avatar account mismatch");
170066
+ }
170067
+ if (data == null) {
170068
+ pendingAvatars.delete(uid);
170069
+ const result = await callFunction("setAvatar", { body: null });
170070
+ if (result?.avatar != null) {
170071
+ throw new Error("avatar clear returned invalid state");
170072
+ }
170073
+ return { version: null };
170074
+ }
170075
+ const bytes = new Uint8Array(encryptedBytes(data, "avatar bytes"));
170076
+ const pending = { bytes, version: null, promise: null };
170077
+ const entries = pendingAvatars.get(uid) || [];
170078
+ pendingAvatars.set(uid, [...entries, pending]);
170079
+ pending.promise = callFunction("setAvatar", {
170080
+ body: cloudBytesBase64(bytes, "avatar bytes")
170081
+ }).then((result) => {
170082
+ const version8 = readAvatarVersion(result?.avatar);
170083
+ if (!version8) {
170084
+ throw new Error("avatar publish returned invalid version");
170085
+ }
170086
+ pending.version = version8;
170087
+ return version8;
170088
+ });
170089
+ try {
170090
+ return { version: await pending.promise };
170091
+ } catch (error) {
170092
+ const active = pendingAvatars.get(uid) || [];
170093
+ const remaining = active.filter((entry) => entry !== pending);
170094
+ if (remaining.length) {
170095
+ pendingAvatars.set(uid, remaining);
170096
+ } else {
170097
+ pendingAvatars.delete(uid);
170098
+ }
170099
+ throw error;
170100
+ }
169651
170101
  }
169652
170102
  async function writeProfileIdentity(identity) {
169653
170103
  if (!identity || typeof identity !== "object" || Array.isArray(identity)) {
@@ -169784,13 +170234,28 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169784
170234
  onUpdate?.(snap.exists() ? snap.data()?.active === true : false, { exists: snap.exists() });
169785
170235
  }, onError);
169786
170236
  }
169787
- async function readPeerAvatar(uid) {
169788
- return readStorageFile(avatarPath(uid));
170237
+ async function readPeerAvatar(uid, { version: version8 = null } = {}) {
170238
+ const avatarVersion = readAvatarVersion(version8);
170239
+ const entries = pendingAvatars.get(uid) || [];
170240
+ if (entries.length) {
170241
+ await Promise.allSettled(entries.map((entry) => entry.promise));
170242
+ const pending = entries.find((entry) => entry.version === avatarVersion);
170243
+ if (pending) {
170244
+ const active = pendingAvatars.get(uid) || [];
170245
+ const remaining = active.filter((entry) => entry !== pending);
170246
+ if (remaining.length) {
170247
+ pendingAvatars.set(uid, remaining);
170248
+ } else {
170249
+ pendingAvatars.delete(uid);
170250
+ }
170251
+ return new Uint8Array(pending.bytes);
170252
+ }
170253
+ }
170254
+ return readStorageFile(avatarPath(uid, avatarVersion));
169789
170255
  }
169790
170256
  async function peerAvatarUrl(uid, { version: version8 = null } = {}) {
169791
170257
  const targetStorage = requireStorage2();
169792
- const url = await getDownloadURL(ref(targetStorage, avatarPath(uid)));
169793
- return avatarUrlWithVersion(url, version8);
170258
+ return getDownloadURL(ref(targetStorage, avatarPath(uid, version8)));
169794
170259
  }
169795
170260
  function profileQueryField(field2, { network } = {}) {
169796
170261
  if (field2 === "walletPK") {
@@ -169841,20 +170306,12 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169841
170306
  const snapshot = await getDocs(roleQuery);
169842
170307
  return peerRecordsFromSnapshot(snapshot);
169843
170308
  }
169844
- async function uploadChatMedia(upload) {
170309
+ async function uploadMedia(upload) {
169845
170310
  const path = upload?.path;
169846
170311
  if (!path)
169847
170312
  throw new Error("media path required");
169848
- getChatMediaFileRef(path);
169849
- await uploadStorageFile(path, upload?.body, upload?.metadata || {});
169850
- return true;
169851
- }
169852
- async function uploadSharedMedia(upload) {
169853
- const path = upload?.path;
169854
- if (!path)
169855
- throw new Error("shared media path required");
169856
- const signed = await callFunction("reserveSharedMediaUpload", {
169857
- sharedId: upload?.sharedId,
170313
+ getMediaFileRef(path);
170314
+ const signed = await callFunction("reserveMediaUpload", {
169858
170315
  path,
169859
170316
  size: uploadByteSize(upload?.body),
169860
170317
  contentType: upload?.metadata?.contentType || "application/octet-stream"
@@ -169862,6 +170319,14 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169862
170319
  await uploadSignedStorageFile(upload, signed?.upload);
169863
170320
  return true;
169864
170321
  }
170322
+ async function uploadChatMedia(upload) {
170323
+ getChatMediaFileRef(upload?.path);
170324
+ return uploadMedia(upload);
170325
+ }
170326
+ async function uploadSharedMedia(upload) {
170327
+ getSharedMediaFileRef(upload?.path);
170328
+ return uploadMedia(upload);
170329
+ }
169865
170330
  async function readChatMedia(path) {
169866
170331
  if (!path)
169867
170332
  throw new Error("media path required");
@@ -170249,12 +170714,13 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170249
170714
  }
170250
170715
  };
170251
170716
  }
170252
- async function pushInbox(recipientUid, ping) {
170717
+ async function pushInbox(recipientUid, ping, options2 = {}) {
170253
170718
  requireUid(recipientUid);
170254
170719
  if (!ping)
170255
170720
  throw new Error("inbox ping required");
170256
170721
  await callFunction("push", {
170257
170722
  recipientUid,
170723
+ notify: options2?.notify !== false,
170258
170724
  ping: {
170259
170725
  v: ping.v,
170260
170726
  epk: ping.epk,
@@ -170293,7 +170759,9 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170293
170759
  payload.onCommitted?.({ chatId, messageId });
170294
170760
  let delivered = true;
170295
170761
  if (payload.inbox?.recipientUid && payload.inbox?.ping) {
170296
- delivered = await pushInbox(payload.inbox.recipientUid, payload.inbox.ping).then(() => true, () => false);
170762
+ delivered = await pushInbox(payload.inbox.recipientUid, payload.inbox.ping, {
170763
+ notify: payload.inbox.notify !== false
170764
+ }).then(() => true, () => false);
170297
170765
  }
170298
170766
  return { chatId, messageId, delivered };
170299
170767
  }
@@ -170546,18 +171014,19 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170546
171014
  }
170547
171015
  async function banAdminUser(uid, feature = "chat") {
170548
171016
  requireUid(uid);
170549
- await setDoc(doc(db, "moderation", uid), { banned: { [feature]: { until: null } } }, { merge: true });
170550
171017
  if (feature === "avatar") {
170551
- await deleteProfileAvatar(uid).catch((error) => {
170552
- if (error?.code !== "storage/object-not-found") {
170553
- throw error;
170554
- }
170555
- });
171018
+ await callFunction("setAvatarModeration", { uid, banned: true });
171019
+ return true;
170556
171020
  }
171021
+ await setDoc(doc(db, "moderation", uid), { banned: { [feature]: { until: null } } }, { merge: true });
170557
171022
  return true;
170558
171023
  }
170559
171024
  async function unbanAdminUser(uid, feature = "chat") {
170560
171025
  requireUid(uid);
171026
+ if (feature === "avatar") {
171027
+ await callFunction("setAvatarModeration", { uid, banned: false });
171028
+ return true;
171029
+ }
170561
171030
  await setDoc(doc(db, "moderation", uid), { banned: { [feature]: deleteField() } }, { merge: true });
170562
171031
  return true;
170563
171032
  }
@@ -170595,9 +171064,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170595
171064
  write: writeProfileIdentity
170596
171065
  },
170597
171066
  avatar: {
170598
- write: writeProfileAvatar,
170599
- upload: uploadProfileAvatar,
170600
- delete: deleteProfileAvatar
171067
+ set: setProfileAvatar
170601
171068
  }
170602
171069
  },
170603
171070
  private: {
@@ -173187,13 +173654,6 @@ function cleanMessage(value, limit2, label = "message") {
173187
173654
  throw new Error(`${label} must be ${limit2} characters or fewer`);
173188
173655
  return message;
173189
173656
  }
173190
- function readAvatarGeneration(value) {
173191
- const version8 = Number(value);
173192
- if (!Number.isSafeInteger(version8) || version8 <= 0) {
173193
- throw new Error("avatar upload did not return a valid generation");
173194
- }
173195
- return version8;
173196
- }
173197
173657
  function passkeyRows(linksList, passkeys, currentPasskeyId) {
173198
173658
  return [...linksList, ...passkeys].sort((left, right) => (right.createdAt || 0) - (left.createdAt || 0)).map((row) => ({
173199
173659
  ...row,
@@ -173281,23 +173741,17 @@ function createRuntimeProductActions({
173281
173741
  const user = await getUser();
173282
173742
  if (!data)
173283
173743
  throw new Error("avatar data required");
173284
- const result = await cloud.user.profile.avatar.upload(user.uid, data, {
173285
- contentType: options2.contentType || "image/webp"
173286
- });
173287
- const avatarVersion = readAvatarGeneration(result?.generation);
173288
- await cloud.user.profile.avatar.write(user.uid, avatarVersion);
173289
- await user.refetchAvatar?.({ version: avatarVersion, optimistic: true });
173290
- return { avatar: result?.url || null, avatarVersion };
173744
+ if (options2.contentType && options2.contentType !== "image/webp") {
173745
+ throw new Error("avatar content type must be image/webp");
173746
+ }
173747
+ const result = await cloud.user.profile.avatar.set(user.uid, data);
173748
+ await user.refetchAvatar?.({ version: result.version });
173749
+ const updated = await getUser();
173750
+ return { avatar: updated.avatar || null, avatarVersion: result.version };
173291
173751
  },
173292
173752
  async deleteAvatar() {
173293
173753
  const user = await getUser();
173294
- try {
173295
- await cloud.user.profile.avatar.delete(user.uid);
173296
- } catch (error) {
173297
- if (error?.code !== "storage/object-not-found")
173298
- throw error;
173299
- }
173300
- await cloud.user.profile.avatar.write(user.uid, null);
173754
+ await cloud.user.profile.avatar.set(user.uid, null);
173301
173755
  user.clearAvatar?.();
173302
173756
  return { deleted: true };
173303
173757
  }
@@ -174473,6 +174927,34 @@ class ProfileStore {
174473
174927
  var FILE_MODE2 = 384, DIR_MODE2 = 448;
174474
174928
  var init_storage = () => {};
174475
174929
 
174930
+ // ../../shared/actioncopy.js
174931
+ var ACCOUNT_DELETE_PREPARE = "you should export or empty your wallet first.", ACCOUNT_DELETE_CONDITION = "if you delete your account,", ACCOUNT_DELETE_CONSEQUENCE = "you will permanently lose access to your funds and chats.", PASSKEY_DELETE_PENDING = "deleting a pending link disables it.", PASSKEY_DELETE_VERIFIED = "deleting a verified passkey will log out any session currently using it.", PASSKEY_DELETE_LIMITS = "you can’t delete the passkey used by this session or the account’s last passkey.", ACCOUNT_DELETE_COPY, BLOCK_USER_BODY = "they will no longer be able to message you, and this chat will be permanently deleted.", UNBLOCK_USER_BODY = "they will be able to message you again.", CHAT_DELETE_BODY = "this chat will be permanently deleted.", PASSKEY_DELETE_COPY, WALLET_EXPORT_COPY, WITHDRAWAL_COPY, LOGOUT_ALL_DEVICES_BODY = "This will sign your account out on every connected device. If you suspect someone else has access, logging out may not be enough: they may already have copied your wallet recovery phrase or other sensitive information. Move your funds to a safe wallet only you control as soon as possible, then remove sensitive information from this account. Information already copied to another device cannot be erased remotely.";
174932
+ var init_actioncopy = __esm(() => {
174933
+ ACCOUNT_DELETE_COPY = Object.freeze({
174934
+ prepare: ACCOUNT_DELETE_PREPARE,
174935
+ condition: ACCOUNT_DELETE_CONDITION,
174936
+ consequence: ACCOUNT_DELETE_CONSEQUENCE,
174937
+ body: `${ACCOUNT_DELETE_PREPARE} ${ACCOUNT_DELETE_CONDITION} ${ACCOUNT_DELETE_CONSEQUENCE}`
174938
+ });
174939
+ PASSKEY_DELETE_COPY = Object.freeze({
174940
+ pendingBody: PASSKEY_DELETE_PENDING,
174941
+ body: PASSKEY_DELETE_VERIFIED,
174942
+ description: `${PASSKEY_DELETE_PENDING} ${PASSKEY_DELETE_VERIFIED}`,
174943
+ infoBody: `${PASSKEY_DELETE_PENDING} ${PASSKEY_DELETE_LIMITS} ${PASSKEY_DELETE_VERIFIED}`
174944
+ });
174945
+ WALLET_EXPORT_COPY = Object.freeze({
174946
+ networkLead: "this is not a bitcoin wallet.",
174947
+ networkBody: "you cannot use it like a normal bitcoin wallet. you can only use it with the spark network: with a new account on this platform, on a different platform that uses spark wallets, or directly through the spark sdk.",
174948
+ withdrawBody: "if you do not want to use this account anymore, withdraw your funds back to a bitcoin wallet instead.",
174949
+ secretBody: "the mnemonic gives full control of this Spark wallet. reveal it only somewhere private."
174950
+ });
174951
+ WITHDRAWAL_COPY = Object.freeze({
174952
+ feeBody: "you can withdraw your funds back to any bitcoin address. bitcoin transactions are not free. validators need to get paid.",
174953
+ reviewBody: "veyl gets a live Spark withdrawal quote after you press withdraw. review the destination address, the amount leaving your wallet, the quoted fee, and the amount that reaches the address before confirming.",
174954
+ irreversibleBody: "once confirmed, bitcoin withdrawals are irreversible."
174955
+ });
174956
+ });
174957
+
174476
174958
  // src/commands.js
174477
174959
  import { Buffer as Buffer4 } from "node:buffer";
174478
174960
  import { chmod as chmod3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
@@ -174640,6 +175122,7 @@ function cliHelpLines() {
174640
175122
  }
174641
175123
  var stringProp, numberProp, booleanProp, objectProp, stringArrayProp, MIME_BY_EXTENSION, TYPE_BY_MIME, optionsOnly = (_args, context) => ({ ...context.commandOptions }), firstArg = (key) => (args, context) => ({ ...context.commandOptions, [key]: args[0] }), peerInput, peerMessageInput = (args, context) => ({ ...context.commandOptions, peer: args[0], message: args.slice(1).join(" ") }), peerMessageIdInput = (args, context) => ({ ...context.commandOptions, peer: args[0], messageId: args[1] }), peerCidInput = (args, context) => ({ ...context.commandOptions, peer: args[0], cid: args[1] }), peerMessageActionInput = (args, context) => ({ ...context.commandOptions, peer: args[0], messageId: args[1], message: args.slice(2).join(" ") }), peerEmojiInput = (args, context) => ({ ...context.commandOptions, peer: args[0], messageId: args[1], emoji: args.slice(2).join(" ") }), peerSatsInput = (args, context) => ({ ...context.commandOptions, peer: args[0], sats: args[1] }), invoiceInput, requestInput, idInput, withdrawalInput = (args, context) => ({ ...context.commandOptions, address: args[0], sats: args[1] }), inviteLinkInput = (args, context) => ({ ...context.commandOptions, kind: args[0], sats: args[1] }), HEADLESS_COMMANDS, commandByName, cliPaths, MCP_TOOLS, groups, COMMANDS;
174642
175124
  var init_commands = __esm(() => {
175125
+ init_actioncopy();
174643
175126
  stringProp = Object.freeze({ type: "string" });
174644
175127
  numberProp = Object.freeze({ type: "number" });
174645
175128
  booleanProp = Object.freeze({ type: "boolean" });
@@ -174716,12 +175199,12 @@ var init_commands = __esm(() => {
174716
175199
  invokeCli: async (client2, input, context) => client2.account.acceptTerms({ ...input, termsVersion: await context.acceptTerms(input.termsVersion) })
174717
175200
  }),
174718
175201
  command({ name: "account_logout", group: "account", apiName: "logout", description: "Lock and sign out this runtime.", usage: "account logout", cliInput: optionsOnly, access: "write", invoke: (client2) => client2.account.logout() }),
174719
- command({ name: "account_logout_all", group: "account", apiName: "logoutAll", description: "Revoke all product auth sessions.", usage: "account logout-all", cliInput: optionsOnly, access: "destructive", invoke: (client2) => client2.account.logoutAll() }),
174720
- command({ name: "account_delete", group: "account", apiName: "delete", description: "Delete the account, chats, and local profile.", properties: { confirm: booleanProp }, required: ["confirm"], usage: "account delete", cliInput: optionsOnly, access: "destructive", invoke: (client2, input) => client2.account.delete(input) }),
175202
+ command({ name: "account_logout_all", group: "account", apiName: "logoutAll", description: LOGOUT_ALL_DEVICES_BODY, usage: "account logout-all", cliInput: optionsOnly, access: "destructive", invoke: (client2) => client2.account.logoutAll() }),
175203
+ command({ name: "account_delete", group: "account", apiName: "delete", description: ACCOUNT_DELETE_COPY.body, properties: { confirm: booleanProp }, required: ["confirm"], usage: "account delete", cliInput: optionsOnly, access: "destructive", invoke: (client2, input) => client2.account.delete(input) }),
174721
175204
  command({ name: "vault_create", group: "vault", apiName: "create", description: "Create and upload a locally encrypted vault.", properties: { secret: stringProp, saveSecret: booleanProp }, usage: "vault create", cliInput: vaultInput, access: "write", secret: true, invoke: (client2, input) => client2.vault.create(input) }),
174722
175205
  command({ name: "vault_unlock", group: "vault", apiName: "unlock", description: "Unlock the local vault.", properties: { secret: stringProp }, usage: "vault unlock", cliInput: vaultInput, access: "write", secret: true, invoke: (client2, input) => client2.vault.unlock(input) }),
174723
175206
  command({ name: "vault_lock", group: "vault", apiName: "lock", description: "Lock wallet and chat state.", usage: "vault lock", cliInput: optionsOnly, access: "write", invoke: (client2) => client2.vault.lock() }),
174724
- command({ name: "vault_export", group: "vault", apiName: "export", description: "Export the Spark wallet mnemonic.", properties: { secret: stringProp }, usage: "vault export", cliInput: vaultInput, mcp: false, secret: true, invoke: (client2, input) => client2.vault.export(input) }),
175207
+ command({ name: "vault_export", group: "vault", apiName: "export", description: WALLET_EXPORT_COPY.secretBody, properties: { secret: stringProp }, usage: "vault export", cliInput: vaultInput, mcp: false, secret: true, invoke: (client2, input) => client2.vault.export(input) }),
174725
175208
  command({ name: "profile_show", group: "profile", apiName: "show", description: "Show the public account profile.", usage: "profile show", cliInput: optionsOnly, invoke: (client2) => client2.profile.show() }),
174726
175209
  command({
174727
175210
  name: "profile_avatar_set",
@@ -174747,8 +175230,8 @@ var init_commands = __esm(() => {
174747
175230
  command({ name: "peers_search", group: "peers", apiName: "search", description: "Search public Veyl profiles.", properties: { query: stringProp, count: numberProp }, required: ["query"], usage: "peers search <query>", cliInput: (args, context) => ({ ...context.commandOptions, query: args.join(" ") }), invoke: (client2, input) => client2.peers.search(input.query, input) }),
174748
175231
  command({ name: "peers_list", group: "peers", apiName: "list", description: "List recent peers.", properties: { count: numberProp, recent: booleanProp }, usage: "peers list", cliInput: optionsOnly, invoke: (client2, input) => client2.peers.list(input) }),
174749
175232
  command({ name: "peers_blocked", group: "peers", apiName: "blocked", description: "List blocked peers.", usage: "peers blocked", cliInput: optionsOnly, invoke: (client2) => client2.peers.blocked() }),
174750
- command({ name: "peers_block", group: "peers", apiName: "block", description: "Block a peer and hide their local chat.", properties: { peer: stringProp }, required: ["peer"], usage: "peers block @name", cliInput: peerInput, access: "destructive", invoke: (client2, input) => client2.peers.block(input.peer) }),
174751
- command({ name: "peers_unblock", group: "peers", apiName: "unblock", description: "Unblock a peer.", properties: { peer: stringProp }, required: ["peer"], usage: "peers unblock @name", cliInput: peerInput, access: "write", invoke: (client2, input) => client2.peers.unblock(input.peer) }),
175233
+ command({ name: "peers_block", group: "peers", apiName: "block", description: BLOCK_USER_BODY, properties: { peer: stringProp }, required: ["peer"], usage: "peers block @name", cliInput: peerInput, access: "destructive", invoke: (client2, input) => client2.peers.block(input.peer) }),
175234
+ command({ name: "peers_unblock", group: "peers", apiName: "unblock", description: UNBLOCK_USER_BODY, properties: { peer: stringProp }, required: ["peer"], usage: "peers unblock @name", cliInput: peerInput, access: "write", invoke: (client2, input) => client2.peers.unblock(input.peer) }),
174752
175235
  command({ name: "chat_list", group: "chat", apiName: "list", description: "List chats.", properties: { count: numberProp }, usage: "chat list", cliInput: optionsOnly, invoke: (client2, input) => client2.chat.list(input) }),
174753
175236
  command({ name: "chat_messages", group: "chat", apiName: "messages", clientMethods: ["read"], description: "Read messages and mark the latest peer message as read.", properties: { peer: stringProp, count: numberProp }, required: ["peer"], usage: "chat messages @name", cliInput: peerInput, access: "write", invoke: (client2, input) => client2.chat.read(input.peer, input) }),
174754
175237
  command({ name: "chat_mark_read", group: "chat", apiName: "markRead", description: "Mark the latest peer message as read.", properties: { peer: stringProp }, required: ["peer"], usage: "chat mark-read @name", cliInput: peerInput, access: "write", invoke: (client2, input) => client2.chat.markRead(input.peer, input) }),
@@ -174761,7 +175244,7 @@ var init_commands = __esm(() => {
174761
175244
  command({ name: "chat_unsave", group: "chat", apiName: "unsave", description: "Return one message to temporary retention.", properties: { peer: stringProp, messageId: stringProp }, required: ["peer", "messageId"], usage: "chat unsave @name message-id", cliInput: peerMessageIdInput, access: "write", invoke: (client2, { peer, messageId, ...options2 }) => client2.chat.unsave(peer, messageId, options2) }),
174762
175245
  command({ name: "chat_edit", group: "chat", apiName: "edit", clientMethods: ["update"], description: "Edit a text message you sent.", properties: { peer: stringProp, messageId: stringProp, message: stringProp }, required: ["peer", "messageId", "message"], usage: "chat edit @name message-id message", cliInput: peerMessageActionInput, access: "write", invoke: (client2, { peer, messageId, message, ...options2 }) => client2.chat.update(peer, messageId, message, options2) }),
174763
175246
  command({ name: "chat_delete_message", group: "chat", apiName: "deleteMessage", clientMethods: ["delete"], description: "Delete one message immediately.", properties: { peer: stringProp, messageId: stringProp }, required: ["peer", "messageId"], usage: "chat delete-message @name message-id", cliInput: peerMessageIdInput, access: "destructive", invoke: (client2, { peer, messageId, ...options2 }) => client2.chat.delete(peer, messageId, options2) }),
174764
- command({ name: "chat_delete", group: "chat", apiName: "deleteChat", description: "Delete one chat.", properties: { peer: stringProp, cleanup: booleanProp }, required: ["peer"], usage: "chat delete @name", cliInput: peerInput, access: "destructive", invoke: (client2, input) => client2.chat.deleteChat(input.peer, input) }),
175247
+ command({ name: "chat_delete", group: "chat", apiName: "deleteChat", description: CHAT_DELETE_BODY, properties: { peer: stringProp, cleanup: booleanProp }, required: ["peer"], usage: "chat delete @name", cliInput: peerInput, access: "destructive", invoke: (client2, input) => client2.chat.deleteChat(input.peer, input) }),
174765
175248
  command({ name: "chat_retention", group: "chat", apiName: "retention", description: "Set chat retention to seen or 24h.", properties: { peer: stringProp, retention: stringProp }, required: ["peer", "retention"], usage: "chat retention @name seen|24h", cliInput: (args, context) => ({ ...context.commandOptions, peer: args[0], retention: args[1] }), access: "write", invoke: (client2, input) => client2.chat.retention(input.peer, input.retention, input) }),
174766
175249
  command({
174767
175250
  name: "chat_send_file",
@@ -174846,12 +175329,12 @@ var init_commands = __esm(() => {
174846
175329
  command({ name: "lightning_send", group: "lightning", apiName: "send", description: "Read a Lightning send request.", properties: { id: stringProp }, required: ["id"], usage: "lightning send id", cliInput: idInput, money: true, invoke: (client2, input) => client2.lightning.send(input.id, input) }),
174847
175330
  command({ name: "withdrawal_quote", group: "withdrawal", apiName: "quote", description: "Quote an on-chain withdrawal.", properties: { address: stringProp, sats: numberProp }, required: ["address", "sats"], usage: "withdrawal quote address sats", cliInput: withdrawalInput, access: "write", money: true, invoke: (client2, input) => client2.withdrawal.quote(input.address, input.sats, input) }),
174848
175331
  command({ name: "withdrawal_prepare", group: "withdrawal", apiName: "prepare", description: "Prepare an on-chain withdrawal review.", properties: { address: stringProp, sats: numberProp, speed: stringProp, deductFeeFromWithdrawalAmount: booleanProp }, required: ["address", "sats"], usage: "withdrawal prepare address sats", cliInput: withdrawalInput, access: "write", money: true, invoke: (client2, input) => client2.withdrawal.prepare(input.address, input.sats, input) }),
174849
- command({ name: "withdrawal_confirm", group: "withdrawal", apiName: "confirm", description: "Confirm an on-chain withdrawal.", properties: { address: stringProp, sats: numberProp, speed: stringProp, feeQuoteId: stringProp, feeAmountSats: numberProp, deductFeeFromWithdrawalAmount: booleanProp }, required: ["address", "sats"], usage: "withdrawal confirm address sats", cliInput: withdrawalInput, access: "destructive", money: true, invoke: (client2, input) => client2.withdrawal.confirm(input.address, input.sats, input) }),
175332
+ command({ name: "withdrawal_confirm", group: "withdrawal", apiName: "confirm", description: WITHDRAWAL_COPY.irreversibleBody, properties: { address: stringProp, sats: numberProp, speed: stringProp, feeQuoteId: stringProp, feeAmountSats: numberProp, deductFeeFromWithdrawalAmount: booleanProp }, required: ["address", "sats"], usage: "withdrawal confirm address sats", cliInput: withdrawalInput, access: "destructive", money: true, invoke: (client2, input) => client2.withdrawal.confirm(input.address, input.sats, input) }),
174850
175333
  command({ name: "invite_link", group: "invite", apiName: "link", description: "Create a join, chat, send, or request invite link.", properties: { kind: stringProp, sats: numberProp }, usage: "invite link [join|chat|send|request] [sats]", cliInput: inviteLinkInput, invoke: (client2, input) => client2.invite.link(input) }),
174851
175334
  command({ name: "invite_read", group: "invite", apiName: "read", description: "Parse a Veyl invite URL.", properties: { value: stringProp }, required: ["value"], usage: "invite read url", cliInput: firstArg("value"), invoke: (client2, input) => client2.invite.read(input.value) }),
174852
175335
  command({ name: "passkeys_list", group: "passkeys", apiName: "list", description: "List passkeys and pending links.", usage: "passkeys list", cliInput: optionsOnly, invoke: (client2, input) => client2.passkeys.list(input) }),
174853
175336
  command({ name: "passkeys_link", group: "passkeys", apiName: "link", clientMethods: ["createLink"], description: "Create a one-time passkey link.", properties: { webUrl: stringProp }, usage: "passkeys link", cliInput: optionsOnly, mcp: false, access: "write", secret: true, invoke: (client2, input) => client2.passkeys.createLink(input) }),
174854
- command({ name: "passkeys_delete", group: "passkeys", apiName: "delete", description: "Delete a pending link or verified passkey.", properties: { id: stringProp, webUrl: stringProp }, required: ["id"], usage: "passkeys delete id", cliInput: (args, context) => ({ ...context.commandOptions, id: args[0], webUrl: context.options.webUrl, onUrl: context.onPasskeyUrl }), access: "destructive", invoke: (client2, input) => client2.passkeys.delete(input.id, input) }),
175337
+ command({ name: "passkeys_delete", group: "passkeys", apiName: "delete", description: PASSKEY_DELETE_COPY.description, properties: { id: stringProp, webUrl: stringProp }, required: ["id"], usage: "passkeys delete id", cliInput: (args, context) => ({ ...context.commandOptions, id: args[0], webUrl: context.options.webUrl, onUrl: context.onPasskeyUrl }), access: "destructive", invoke: (client2, input) => client2.passkeys.delete(input.id, input) }),
174855
175338
  command({ name: "support_feedback", group: "support", apiName: "feedback", description: "Send product feedback.", properties: { message: stringProp, type: stringProp, context: stringProp }, required: ["message"], usage: "support feedback message", cliInput: (args, context) => ({ ...context.commandOptions, message: args.join(" ") }), access: "write", invoke: (client2, input) => client2.support.feedback(input.message, input) }),
174856
175339
  command({ name: "support_bug", group: "support", apiName: "bug", description: "Submit a bug report.", properties: { message: stringProp }, required: ["message"], usage: "support bug message", cliInput: (args, context) => ({ ...context.commandOptions, message: args.join(" ") }), access: "write", invoke: (client2, input) => client2.support.bug(input.message, input) }),
174857
175340
  command({ name: "support_report", group: "support", apiName: "report", description: "Report a peer or one harmful message.", properties: { peer: stringProp, messageId: stringProp, note: stringProp, includeAttachment: booleanProp }, required: ["peer"], usage: "support report @name [message-id]", cliInput: (args, context) => ({ ...context.commandOptions, peer: args[0], messageId: args[1] }), access: "write", invoke: (client2, input) => client2.support.report(input.peer, input) }),
@@ -174890,7 +175373,7 @@ var package_default;
174890
175373
  var init_package = __esm(() => {
174891
175374
  package_default = {
174892
175375
  name: "veyl",
174893
- version: "0.32.1",
175376
+ version: "0.41.0",
174894
175377
  private: true,
174895
175378
  license: "Apache-2.0",
174896
175379
  workspaces: {
@@ -174906,16 +175389,16 @@ var init_package = __esm(() => {
174906
175389
  "@noble/curves": "^2.2.0",
174907
175390
  "@noble/hashes": "^2.2.0",
174908
175391
  "@scure/bip39": "^2.2.0",
174909
- "@tailwindcss/postcss": "^4.3.2",
175392
+ "@tailwindcss/postcss": "^4.3.3",
174910
175393
  bech32: "^2.0.0",
174911
175394
  firebase: "^12.16.0",
174912
175395
  "lucide-react": "^1.24.0",
174913
175396
  next: "16.3.0-preview.6",
174914
175397
  "next-themes": "^0.4.6",
174915
- postcss: "^8.5.19",
174916
- react: "19.2.7",
174917
- "react-dom": "19.2.7",
174918
- tailwindcss: "^4.3.2",
175398
+ postcss: "^8.5.22",
175399
+ react: "19.2.8",
175400
+ "react-dom": "19.2.8",
175401
+ tailwindcss: "^4.3.3",
174919
175402
  "tw-animate-css": "^1.4.0"
174920
175403
  }
174921
175404
  },
@@ -175687,6 +176170,7 @@ class VeylHeadlessClient {
175687
176170
  this.userOwner.setNetwork(network);
175688
176171
  }
175689
176172
  });
176173
+ await opened.walletReady;
175690
176174
  this.userSnapshot = this.userOwner.getSnapshot();
175691
176175
  } catch (error) {
175692
176176
  if (opened)