@glyphteck/veyl 0.31.1 → 0.35.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.
Files changed (3) hide show
  1. package/dist/cli.js +649 -383
  2. package/dist/index.js +649 -383
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5906,9 +5906,8 @@ var init_vaultsignature = __esm(() => {
5906
5906
  DELETE_ACCOUNT: "delete-account",
5907
5907
  DELETE_PASSKEY: "delete-passkey",
5908
5908
  LOGOUT_ALL_DEVICES: "logout-all-devices",
5909
- SET_CHAT_IDENTITY: "set-chat-identity",
5910
- SET_SETTINGS: "set-settings",
5911
- SET_WALLET_IDENTITY: "set-wallet-identity"
5909
+ SET_IDENTITIES: "set-identities",
5910
+ SET_SETTINGS: "set-settings"
5912
5911
  });
5913
5912
  });
5914
5913
 
@@ -6217,7 +6216,7 @@ var init_seed = __esm(() => {
6217
6216
  });
6218
6217
 
6219
6218
  // ../../shared/config.js
6220
- 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;
6221
6220
  var init_config = __esm(() => {
6222
6221
  MINUTE_MS = 60 * MS_PER_SECOND;
6223
6222
  HOUR_MS = 60 * MINUTE_MS;
@@ -7744,20 +7743,17 @@ function expectedSigningKeys(pair, signingKeysByChatKey) {
7744
7743
  }
7745
7744
  return values;
7746
7745
  }
7747
- async function verifyExpectedSignatureBeforeOpen(pair, sig, bytes, options = {}) {
7746
+ async function preverifyExpectedSigner(pair, sig, bytes, options = {}) {
7748
7747
  if (!options?.crypto?.preverifyBeforeOpen) {
7749
- return true;
7750
- }
7751
- const signingKeysByChatKey = expectedSigningKeys(pair, options?.signingKeysByChatKey);
7752
- if (signingKeysByChatKey.length < 2) {
7753
- return true;
7748
+ return "";
7754
7749
  }
7755
- for (const signingPublicKey of signingKeysByChatKey) {
7750
+ const signingPublicKeys = expectedSigningKeys(pair, options?.signingKeysByChatKey);
7751
+ for (const signingPublicKey of signingPublicKeys) {
7756
7752
  if (await verifyMsgSignature(signingPublicKey, sig, bytes, options)) {
7757
- return true;
7753
+ return signingPublicKey;
7758
7754
  }
7759
7755
  }
7760
- return false;
7756
+ return null;
7761
7757
  }
7762
7758
  async function sealMsg(pair, message, options = {}) {
7763
7759
  const head = getHead(pair, message);
@@ -7778,14 +7774,16 @@ async function openMsg(pair, data, options = {}) {
7778
7774
  const { suite, flags, nonce, sig, ct } = unpackMsgBody(data.body);
7779
7775
  const aad = getMsgAad(pair, head, suite, flags);
7780
7776
  const signatureInput = getSignatureInput(aad, nonce, ct);
7781
- if (!await verifyExpectedSignatureBeforeOpen(pair, sig, signatureInput, options)) {
7777
+ const preverifiedSigner = await preverifyExpectedSigner(pair, sig, signatureInput, options);
7778
+ if (preverifiedSigner == null) {
7782
7779
  throw new Error("invalid chat action signature");
7783
7780
  }
7784
7781
  const plaintext = await openMsgPlaintext(pair, nonce, ct, aad, options);
7785
7782
  try {
7786
7783
  const action = JSON.parse(decoder.decode(plaintext));
7787
7784
  const normalized = normalizeOpenedAction(pair, head, action, options);
7788
- 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) {
7789
7787
  throw new Error("invalid chat action signature");
7790
7788
  }
7791
7789
  return {
@@ -7859,40 +7857,120 @@ var init_protocol = __esm(() => {
7859
7857
  });
7860
7858
 
7861
7859
  // ../../shared/chat/pairs.js
7860
+ function accountGeneration(chatPK) {
7861
+ return accountGenerations.get(cleanText(chatPK)) || 0;
7862
+ }
7862
7863
  function getChatPairKey(chatPK, peerChatPK, chatId = "") {
7863
7864
  if (!chatPK || !peerChatPK)
7864
7865
  return null;
7865
7866
  return `${cleanText(chatPK)}|${cleanText(peerChatPK)}|${cleanText(chatId)}`;
7866
7867
  }
7867
- function clearChatPairCache() {
7868
- for (const pair of pairCache.values()) {
7869
- closeChatPair(pair);
7868
+ function closeEntry(entry) {
7869
+ if (!entry || entry.closed || entry.leases > 0) {
7870
+ return;
7871
+ }
7872
+ entry.closed = true;
7873
+ closeChatPair(entry.pair);
7874
+ }
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);
7870
7889
  }
7871
- pairCache.clear();
7872
7890
  }
7873
- async function getCachedPair(chatPK, chatPrivKey, peerChatPK, options = {}) {
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);
7874
7924
  const chatId = cleanText(options?.chatId);
7875
7925
  const key = getChatPairKey(chatPK, peerChatPK, chatId);
7926
+ const generation = cacheGeneration;
7927
+ const accountVersion = accountGeneration(account);
7876
7928
  if (!key) {
7877
- 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
+ };
7878
7935
  }
7879
- const cached = pairCache.get(key);
7880
- if (cached)
7881
- return cached;
7882
- const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
7883
- pairCache.set(key, pair);
7884
- if (pairCache.size > MAX_PAIR_CACHE) {
7885
- const firstKey = pairCache.keys().next().value;
7886
- if (firstKey) {
7887
- closeChatPair(pairCache.get(firstKey));
7888
- 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
+ };
7889
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();
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();
7890
7970
  }
7891
- return pair;
7892
7971
  }
7893
7972
  async function resolveLinkId(chatPK, chatPrivKey, peerChatPK) {
7894
- const pair = await getCachedPair(chatPK, chatPrivKey, peerChatPK);
7895
- return pair?.linkId || null;
7973
+ return withCachedPair(chatPK, chatPrivKey, peerChatPK, {}, (pair) => pair?.linkId || null);
7896
7974
  }
7897
7975
  async function resolvePeerChat(cloud, chatPK, chatPrivateKey, peerChatPK, options = {}) {
7898
7976
  const existingChatId = cleanText(options?.chatId);
@@ -7923,13 +8001,14 @@ async function resolvePeerChat(cloud, chatPK, chatPrivateKey, peerChatPK, option
7923
8001
  exists: linkChat?.exists === true
7924
8002
  };
7925
8003
  }
7926
- var pairCache, MAX_PAIR_CACHE;
8004
+ var pairCache, MAX_PAIR_CACHE, cacheGeneration = 0, accountGenerations;
7927
8005
  var init_pairs = __esm(() => {
7928
8006
  init_config();
7929
8007
  init_chat();
7930
8008
  init_protocol();
7931
8009
  pairCache = new Map;
7932
8010
  MAX_PAIR_CACHE = CHAT_PAIR_CACHE_LIMIT;
8011
+ accountGenerations = new Map;
7933
8012
  });
7934
8013
 
7935
8014
  // ../../shared/utils/diagnostics.js
@@ -7998,11 +8077,9 @@ function getBootWalletClass(SparkWallet, { enableTokenSync = false } = {}) {
7998
8077
  function bytesToHex4(bytes) {
7999
8078
  return Array.from(bytes || []).map((b) => b.toString(16).padStart(2, "0")).join("");
8000
8079
  }
8001
- async function bootWallet(walletMnemonic, user, { SparkWallet, cloud, network, enableTokenSync = false, diag = null } = {}) {
8080
+ async function bootWallet(walletMnemonic, user, { SparkWallet, network, enableTokenSync = false, diag = null } = {}) {
8002
8081
  if (!SparkWallet)
8003
8082
  throw new Error("SparkWallet missing");
8004
- if (!cloud)
8005
- throw new Error("cloud missing");
8006
8083
  if (!network)
8007
8084
  throw new Error("network missing");
8008
8085
  const startedAt = Date.now();
@@ -8037,53 +8114,41 @@ async function bootWallet(walletMnemonic, user, { SparkWallet, cloud, network, e
8037
8114
  const walletPK = lowerText(idPk);
8038
8115
  const expectedWalletPK = resolveWalletPK(user, network);
8039
8116
  markDone(diag, "vault.bootWallet.identity", identityStartedAt);
8040
- const setupStartedAt = Date.now();
8117
+ let walletPKNeedsWrite = false;
8041
8118
  if (!expectedWalletPK) {
8042
- markDiag(diag, "vault.bootWallet.setup.start", { reason: "missing-wallet-pk" });
8043
- await cloud.user.profile.walletpk.write(idPk, { network });
8044
- markDone(diag, "vault.bootWallet.setup", setupStartedAt, { wrote: true });
8119
+ walletPKNeedsWrite = true;
8045
8120
  } else if (!sameText(expectedWalletPK, walletPK)) {
8046
8121
  throw new Error("wallet identity mismatch for account");
8047
8122
  } else if (!hasWalletPKForNetwork(user, network)) {
8048
- markDiag(diag, "vault.bootWallet.setup.start", { reason: "missing-network-wallet-pk" });
8049
- await cloud.user.profile.walletpk.write(idPk, { network });
8050
- markDone(diag, "vault.bootWallet.setup", setupStartedAt, { wrote: true });
8051
- } else {
8052
- markDiag(diag, "vault.bootWallet.setup.skip", { elapsedMs: Date.now() - setupStartedAt });
8123
+ walletPKNeedsWrite = true;
8053
8124
  }
8054
8125
  markDone(diag, "vault.bootWallet", startedAt);
8055
- return { wallet, walletPK };
8126
+ return { wallet, walletPK, walletPKNeedsWrite };
8056
8127
  } catch (error) {
8057
8128
  markError(diag, "vault.bootWallet", startedAt, error);
8058
8129
  throw error;
8059
8130
  }
8060
8131
  }
8061
- async function bootChat(chatSeed, user, { cloud, diag = null } = {}) {
8132
+ async function bootChat(chatSeed, user, { diag = null } = {}) {
8062
8133
  const startedAt = Date.now();
8063
8134
  markDiag(diag, "vault.bootChat.start", { hasChatPK: !!user?.chatPK });
8064
8135
  try {
8065
- if (!cloud)
8066
- throw new Error("cloud missing");
8067
8136
  const keyStartedAt = Date.now();
8068
8137
  const chatKeyPair = getKeyPair(chatSeed);
8069
8138
  chatSeed.fill(0);
8070
8139
  const chatPKHex = bytesToHex4(chatKeyPair.pub);
8071
8140
  markDone(diag, "vault.bootChat.derive", keyStartedAt);
8141
+ let chatPKNeedsWrite = false;
8072
8142
  if (!user.chatPK) {
8073
- const setupStartedAt = Date.now();
8074
- markDiag(diag, "vault.bootChat.setup.start", { reason: "missing-chat-pk" });
8075
- await cloud.user.profile.chatpk.write(chatPKHex);
8076
- markDone(diag, "vault.bootChat.setup", setupStartedAt, { wrote: true });
8143
+ chatPKNeedsWrite = true;
8077
8144
  } else if (!sameText(user.chatPK, chatPKHex)) {
8078
8145
  chatKeyPair.priv.fill(0);
8079
8146
  chatKeyPair.pub.fill(0);
8080
8147
  throw new Error("chat identity mismatch for account");
8081
- } else {
8082
- markDiag(diag, "vault.bootChat.setup.skip", {});
8083
8148
  }
8084
8149
  chatKeyPair.pub.fill(0);
8085
8150
  markDone(diag, "vault.bootChat", startedAt);
8086
- return { chatPrivateKey: chatKeyPair.priv, chatPK: chatPKHex };
8151
+ return { chatPrivateKey: chatKeyPair.priv, chatPK: chatPKHex, chatPKNeedsWrite };
8087
8152
  } catch (error) {
8088
8153
  markError(diag, "vault.bootChat", startedAt, error);
8089
8154
  chatSeed.fill(0);
@@ -8096,14 +8161,16 @@ function lockWallet(wallet) {
8096
8161
  cleanup?.catch?.(() => {});
8097
8162
  } catch {}
8098
8163
  }
8099
- function lockChat(chatPrivateKey) {
8164
+ function lockChat(chatPrivateKey, chatPK = "") {
8100
8165
  try {
8101
8166
  if (chatPrivateKey) {
8102
8167
  chatPrivateKey.fill(0);
8103
8168
  }
8104
8169
  } catch {}
8105
8170
  try {
8106
- clearChatPairCache();
8171
+ if (chatPK) {
8172
+ clearChatPairCache(chatPK);
8173
+ }
8107
8174
  } catch {}
8108
8175
  }
8109
8176
  var init_vault = __esm(() => {
@@ -8123,7 +8190,7 @@ function walletPubkey(value) {
8123
8190
  }
8124
8191
  return key;
8125
8192
  }
8126
- function closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner } = {}) {
8193
+ function closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner } = {}) {
8127
8194
  try {
8128
8195
  vaultAccess?.close?.();
8129
8196
  } catch {}
@@ -8134,7 +8201,7 @@ function closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess
8134
8201
  localCache?.close?.();
8135
8202
  } catch {}
8136
8203
  lockWallet(wallet);
8137
- lockChat(chatPrivateKey);
8204
+ lockChat(chatPrivateKey, chatPK);
8138
8205
  }
8139
8206
  function createAccountSession(resources = {}) {
8140
8207
  let closed = false;
@@ -8225,6 +8292,32 @@ function notifySeedDecrypted(callback, markDiag2, source) {
8225
8292
  failed(error);
8226
8293
  }
8227
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
+ }
8228
8321
  async function openVaultAccountSession(vault, password, options = {}) {
8229
8322
  const {
8230
8323
  uid,
@@ -8266,6 +8359,7 @@ async function openVaultAccountSession(vault, password, options = {}) {
8266
8359
  throw new Error("unlockSettings missing");
8267
8360
  const { decryptSeed: decryptSeed2, unpackVaultSeedData } = vaultCrypto;
8268
8361
  let wallet = null;
8362
+ let chatPK = "";
8269
8363
  let chatPrivateKey = null;
8270
8364
  let localCache = null;
8271
8365
  let masterSeed = null;
@@ -8276,14 +8370,24 @@ async function openVaultAccountSession(vault, password, options = {}) {
8276
8370
  let vaultSigner = null;
8277
8371
  let vaultAccess = null;
8278
8372
  let vaultAccessResult = null;
8373
+ let walletBoot = null;
8279
8374
  let network;
8280
8375
  try {
8281
8376
  onStage?.("unlocking");
8282
8377
  const authStartedAt = Date.now();
8283
8378
  mark(diag, "vault.unlock.session.start", { source });
8284
- await cloud.auth.user.getIdToken(true);
8285
- mark(diag, "vault.unlock.session.done", { elapsedMs: Date.now() - authStartedAt, source });
8286
- requireCurrent(isCurrent);
8379
+ const authResult = cloud.auth.user.getIdToken(true).then(() => {
8380
+ mark(diag, "vault.unlock.session.done", { elapsedMs: Date.now() - authStartedAt, source });
8381
+ return { error: null };
8382
+ }, (error) => {
8383
+ mark(diag, "vault.unlock.session.error", {
8384
+ elapsedMs: Date.now() - authStartedAt,
8385
+ source,
8386
+ code: error?.code || "",
8387
+ message: error?.message || String(error)
8388
+ });
8389
+ return { error };
8390
+ });
8287
8391
  const unpackStartedAt = Date.now();
8288
8392
  const unpacked = unpackVaultSeedData(vault);
8289
8393
  const { salt, iv, ct, kdf, registry: registryEnvelope } = unpacked;
@@ -8312,6 +8416,9 @@ async function openVaultAccountSession(vault, password, options = {}) {
8312
8416
  cleanBytes(walletEntropy, masterSeed);
8313
8417
  walletEntropy = null;
8314
8418
  masterSeed = null;
8419
+ const sessionResult = await authResult;
8420
+ if (sessionResult.error)
8421
+ throw sessionResult.error;
8315
8422
  requireCurrent(isCurrent);
8316
8423
  const vaultSignatureStartedAt = Date.now();
8317
8424
  mark(diag, "vault.unlock.signature.start", { source });
@@ -8332,17 +8439,13 @@ async function openVaultAccountSession(vault, password, options = {}) {
8332
8439
  onStage?.("wallet");
8333
8440
  const walletStartedAt = Date.now();
8334
8441
  mark(diag, "vault.unlock.wallet.start", { source });
8335
- const walletIdentity = await bootWallet2(walletMnemonic, user, { network });
8336
- wallet = walletIdentity?.wallet;
8337
- const walletPK = walletPubkey(walletIdentity?.walletPK);
8338
- mark(diag, "vault.unlock.wallet.done", { elapsedMs: Date.now() - walletStartedAt, source });
8339
- requireCurrent(isCurrent);
8442
+ walletBoot = startWalletBoot(bootWallet2, walletMnemonic, user, { network, diag });
8340
8443
  onStage?.("chat");
8341
8444
  const chatStartedAt = Date.now();
8342
8445
  mark(diag, "vault.unlock.chat.start", { source });
8343
- const chatIdentity = await bootChat2(chatSeed, user);
8446
+ const chatIdentity = await bootChat2(chatSeed, user, { diag });
8344
8447
  chatPrivateKey = chatIdentity?.chatPrivateKey;
8345
- const chatPK = cleanText(chatIdentity?.chatPK);
8448
+ chatPK = cleanText(chatIdentity?.chatPK);
8346
8449
  if (!chatPrivateKey || !chatPK)
8347
8450
  throw new Error("chat identity missing");
8348
8451
  chatSeed = null;
@@ -8360,11 +8463,11 @@ async function openVaultAccountSession(vault, password, options = {}) {
8360
8463
  throw signatureResult.error;
8361
8464
  vaultAccess = signatureResult.access;
8362
8465
  requireCurrent(isCurrent);
8363
- return createAccountSession({
8466
+ const session = createAccountSession({
8364
8467
  uid,
8365
8468
  network,
8366
- wallet,
8367
- walletPK,
8469
+ wallet: null,
8470
+ walletPK: null,
8368
8471
  chatPK,
8369
8472
  chatPrivateKey,
8370
8473
  localCache,
@@ -8372,12 +8475,56 @@ async function openVaultAccountSession(vault, password, options = {}) {
8372
8475
  vaultAccess,
8373
8476
  vaultPK: vaultSigner.publicKey
8374
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;
8375
8521
  } catch (error) {
8522
+ walletBoot?.close();
8376
8523
  if (vaultAccessResult && !vaultAccess) {
8377
8524
  const signatureResult = await vaultAccessResult;
8378
8525
  vaultAccess = signatureResult.access;
8379
8526
  }
8380
- closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner });
8527
+ closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner });
8381
8528
  throw error;
8382
8529
  } finally {
8383
8530
  cleanBytes(masterSeed, walletEntropy, chatSeed, cacheKey, settingsKey);
@@ -8392,6 +8539,7 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
8392
8539
  }
8393
8540
  const walletMnemonic = mnemonicFromWalletEntropy(walletEntropy);
8394
8541
  let wallet = null;
8542
+ let chatPK = "";
8395
8543
  let chatPrivateKey = null;
8396
8544
  let chatPubKey = null;
8397
8545
  let vaultSigner = null;
@@ -8420,10 +8568,11 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
8420
8568
  throw new Error("vault signature uid missing");
8421
8569
  vaultAccess = await cloud.auth.vaultSignature.open(vaultSigner);
8422
8570
  }
8571
+ chatPK = toHex(chatPubKey);
8423
8572
  return createAccountSession({
8424
8573
  wallet,
8425
8574
  walletPK,
8426
- chatPK: toHex(chatPubKey),
8575
+ chatPK,
8427
8576
  chatPrivateKey,
8428
8577
  localCache,
8429
8578
  network,
@@ -8432,7 +8581,7 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
8432
8581
  vaultPK: vaultSigner.publicKey
8433
8582
  });
8434
8583
  } catch (error) {
8435
- closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner });
8584
+ closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner });
8436
8585
  throw error;
8437
8586
  } finally {
8438
8587
  cleanBytes(chatSeed, chatPubKey);
@@ -8459,9 +8608,10 @@ var init_session = __esm(() => {
8459
8608
  });
8460
8609
 
8461
8610
  // ../../shared/community.js
8462
- var COMMUNITY_RULES_VERSION = "2026-05-11.1", COMMUNITY_SECTIONS;
8611
+ var COMMUNITY_RULES_VERSION = "2026-05-11.1", COMMUNITY_SUPPORT_EMAIL, COMMUNITY_SECTIONS;
8463
8612
  var init_community = __esm(() => {
8464
8613
  init_links();
8614
+ COMMUNITY_SUPPORT_EMAIL = links.contact.replace("mailto:", "");
8465
8615
  COMMUNITY_SECTIONS = [
8466
8616
  {
8467
8617
  title: "Use veyl directly and lawfully",
@@ -8501,7 +8651,7 @@ var init_community = __esm(() => {
8501
8651
  {
8502
8652
  title: "Reporting and support",
8503
8653
  body: [
8504
- `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.`,
8505
8655
  "Because veyl uses end-to-end encryption, we may rely on reports, account-level signals, and evidence you submit when reviewing abuse."
8506
8656
  ]
8507
8657
  }
@@ -106357,6 +106507,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106357
106507
  let authSession = 0;
106358
106508
  let userUid = null;
106359
106509
  let settingsKey = null;
106510
+ let settingsStored = null;
106360
106511
  let avatarFetch = { uid: null, key: null, promise: null };
106361
106512
  let blockedSource = state.blocked;
106362
106513
  let blockedSet = new Set(blockedSource);
@@ -106576,6 +106727,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106576
106727
  userUid = null;
106577
106728
  clearSettingsKey(settingsKey);
106578
106729
  settingsKey = null;
106730
+ settingsStored = null;
106579
106731
  if (signedOutUid) {
106580
106732
  keepOnlyCachedAvatar(null);
106581
106733
  }
@@ -106589,6 +106741,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106589
106741
  if (authUidChanged) {
106590
106742
  clearSettingsKey(settingsKey);
106591
106743
  settingsKey = null;
106744
+ settingsStored = null;
106592
106745
  }
106593
106746
  setState((user) => authUidChanged ? { ...defaultUser, authReady: true, uid: authUser.uid } : { ...user, authReady: true, isAdmin: false, adminReady: false });
106594
106747
  readCachedAvatar(authUser.uid).then((cached) => {
@@ -106645,6 +106798,9 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106645
106798
  fromCache: !!info.fromCache,
106646
106799
  pending: !!info.pending
106647
106800
  });
106801
+ if (!info.fromCache) {
106802
+ settingsStored = Object.prototype.hasOwnProperty.call(privateData, "settings");
106803
+ }
106648
106804
  setState((user) => ({
106649
106805
  ...user,
106650
106806
  agreement: privateData.agreement ?? null,
@@ -106652,6 +106808,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106652
106808
  }));
106653
106809
  }), current((error) => {
106654
106810
  markError(diag, "user.settings.snapshot", authStartedAt, error);
106811
+ settingsStored = null;
106655
106812
  setState((user) => ({
106656
106813
  ...user,
106657
106814
  settingsReady: true,
@@ -106755,6 +106912,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106755
106912
  }
106756
106913
  clearSettingsKey(settingsKey);
106757
106914
  settingsKey = null;
106915
+ settingsStored = null;
106758
106916
  userUid = null;
106759
106917
  avatarFetch = { uid: null, key: null, promise: null };
106760
106918
  state = defaultUser;
@@ -106805,7 +106963,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106805
106963
  throw new Error("auth");
106806
106964
  if (!key)
106807
106965
  throw new Error("settings key required");
106808
- const nextSettings = await cloud.user.settings.read(uid, key);
106966
+ const nextSettings = settingsStored === false ? settingsState() : await cloud.user.settings.read(uid, key);
106809
106967
  if (!isCurrentUser(uid, session))
106810
106968
  return nextSettings;
106811
106969
  setSettingsKey(key);
@@ -106829,6 +106987,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106829
106987
  const nextSettings = await cloud.user.settings.write(uid, patch, { currentSettings: state.settings, key: settingsKey });
106830
106988
  if (!isCurrentUser(uid, session))
106831
106989
  return nextSettings;
106990
+ settingsStored = true;
106832
106991
  setState((user) => ({
106833
106992
  ...user,
106834
106993
  settingsReady: true,
@@ -107113,7 +107272,7 @@ function getSubtleCrypto() {
107113
107272
  async function importAesKey(key, usages) {
107114
107273
  return getSubtleCrypto().importKey("raw", toBytes32(key, "file key"), { name: "AES-GCM" }, false, usages);
107115
107274
  }
107116
- async function sealFile(_pair, key, bytes, scope) {
107275
+ async function sealFile(key, bytes, scope) {
107117
107276
  const fileKey = new Uint8Array(toBytes32(key, "file key"));
107118
107277
  try {
107119
107278
  const cryptoKey = await importAesKey(fileKey, ["encrypt"]);
@@ -107276,20 +107435,22 @@ async function toUploadBytes(data) {
107276
107435
  }
107277
107436
  return toBytes(data, "upload bytes");
107278
107437
  }
107279
- async function makeChatFileUploadPayload(pair, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
107280
- const chatId = cleanMediaChatId(pair?.chatId);
107438
+ async function makeChatFileUploadPayload(chatId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
107439
+ const nextChatId = cleanMediaChatId(chatId);
107281
107440
  const mediaId = makeChatMediaId();
107282
- const path = mediaFilePath(chatId, mediaId);
107441
+ const path = mediaFilePath(nextChatId, mediaId);
107283
107442
  const expiresAt = Date.now() + CHAT_MEDIA_TTL_MS2;
107284
107443
  const key = createFileKey();
107285
107444
  try {
107286
107445
  const uploadBytes = await toUploadBytes(data);
107287
107446
  assertChatUploadByteSize(uploadBytes);
107447
+ const body = await sealFile(key, uploadBytes, path);
107448
+ assertChatUploadByteSize(body);
107288
107449
  return {
107289
- chatId,
107450
+ chatId: nextChatId,
107290
107451
  mediaId,
107291
107452
  path,
107292
- body: await sealFile(pair, key, uploadBytes, path),
107453
+ body,
107293
107454
  metadata: {
107294
107455
  contentType: "application/octet-stream",
107295
107456
  cacheControl
@@ -107318,10 +107479,12 @@ async function makeSharedFileUploadPayload(data, { contentType = "application/oc
107318
107479
  try {
107319
107480
  const uploadBytes = await toUploadBytes(data);
107320
107481
  assertChatUploadByteSize(uploadBytes);
107482
+ const body = await sealFile(key, uploadBytes, path);
107483
+ assertChatUploadByteSize(body);
107321
107484
  return {
107322
107485
  sharedId,
107323
107486
  path,
107324
- body: await sealFile(null, key, uploadBytes, path),
107487
+ body,
107325
107488
  metadata: {
107326
107489
  contentType,
107327
107490
  cacheControl
@@ -109247,10 +109410,19 @@ function sortedUniqueValues(items) {
109247
109410
  }
109248
109411
 
109249
109412
  // ../../shared/chat/chats.js
109413
+ function sameSigningKeys(left, right) {
109414
+ if (left === right)
109415
+ return true;
109416
+ const leftKeys = Object.keys(left || {});
109417
+ const rightKeys = Object.keys(right || {});
109418
+ if (leftKeys.length !== rightKeys.length)
109419
+ return false;
109420
+ return leftKeys.every((key) => left[key] === right[key]);
109421
+ }
109250
109422
  function sameChatShape(a, b) {
109251
109423
  if (!a || !b)
109252
109424
  return a === b;
109253
- 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);
109425
+ 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.ts === b.ts && a.startMs === b.startMs && a.unseen === b.unseen && a.settings?.retention === b.settings?.retention && sameChatPreview(a.preview, b.preview);
109254
109426
  }
109255
109427
  function sameChats(prev, next) {
109256
109428
  if (prev.length !== next.length)
@@ -110813,8 +110985,14 @@ function mediaAad(uid, network, id) {
110813
110985
  function partAad(uid, network, part) {
110814
110986
  return encoder.encode(JSON.stringify(["veyl-local-cache-part", CACHE_PART_ENVELOPE_VERSION, String(uid || ""), String(network || ""), String(part || "")]));
110815
110987
  }
110988
+ function encodePayloadData(payload) {
110989
+ return encoder.encode(JSON.stringify({ ...normalizePayload(payload), savedAt: Date.now() }));
110990
+ }
110991
+ function decodePayloadData(raw) {
110992
+ return JSON.parse(decoder.decode(toBytes(raw, "cache payload")));
110993
+ }
110816
110994
  async function sealPayload(key, payload, uid, network) {
110817
- const body = encoder.encode(JSON.stringify({ ...normalizePayload(payload), savedAt: Date.now() }));
110995
+ const body = encodePayloadData(payload);
110818
110996
  try {
110819
110997
  const { iv, ct } = await sealAes(key, body, cacheAad(uid, network));
110820
110998
  return JSON.stringify({
@@ -110836,7 +111014,7 @@ async function openPayloadData(key, raw, uid, network) {
110836
111014
  }
110837
111015
  const plain = await openAes(key, fromHexBytes(envelope.iv, "cache iv"), fromHexBytes(envelope.ct, "cache ciphertext"), cacheAad(uid, network));
110838
111016
  try {
110839
- return JSON.parse(decoder.decode(plain));
111017
+ return decodePayloadData(plain);
110840
111018
  } finally {
110841
111019
  cleanBytes(plain);
110842
111020
  }
@@ -110897,6 +111075,14 @@ function mediaCrypto(uid, network, id, keyBytes) {
110897
111075
  ivBytes: AES_IV_BYTES
110898
111076
  };
110899
111077
  }
111078
+ function payloadCrypto(uid, network, keyBytes) {
111079
+ return {
111080
+ key: keyBytes,
111081
+ aad: cacheAad(uid, network),
111082
+ version: LOCAL_DATA_CACHE_VERSION,
111083
+ ivBytes: AES_IV_BYTES
111084
+ };
111085
+ }
110900
111086
  var MEDIA_ENVELOPE_VERSION = 1, CACHE_PART_ENVELOPE_VERSION = 1;
110901
111087
  var init_crypto = __esm(() => {
110902
111088
  init_aes2();
@@ -110932,11 +111118,32 @@ async function openVaultCache({ key, storage, uid, network }) {
110932
111118
  let writeTimer = null;
110933
111119
  let writeTask = Promise.resolve();
110934
111120
  const partWriteTasks = new Map;
111121
+ let storedPayload = null;
110935
111122
  try {
110936
- const rawPayload = await openPayloadData(cacheKey, await storage.read(), uid, network);
110937
- payload = normalizePayload(rawPayload);
111123
+ storedPayload = await storage.read();
111124
+ if (!storedPayload) {
111125
+ payload = emptyPayload();
111126
+ } else if (typeof storage.openPayload === "function") {
111127
+ const readKey = new Uint8Array(cacheKey);
111128
+ let plain;
111129
+ try {
111130
+ plain = await storage.openPayload(storedPayload, payloadCrypto(uid, network, readKey));
111131
+ payload = normalizePayload(decodePayloadData(plain));
111132
+ } finally {
111133
+ cleanBytes(plain, readKey);
111134
+ }
111135
+ } else {
111136
+ payload = normalizePayload(await openPayloadData(cacheKey, storedPayload, uid, network));
111137
+ }
110938
111138
  } catch {
110939
111139
  payload = emptyPayload();
111140
+ if (storedPayload) {
111141
+ await Promise.all([
111142
+ storage.remove().catch(() => {}),
111143
+ storage.removeAllMedia().catch(() => {}),
111144
+ storage.removeAllParts().catch(() => {})
111145
+ ]);
111146
+ }
110940
111147
  }
110941
111148
  const writeNow = () => {
110942
111149
  if (closed) {
@@ -110954,10 +111161,12 @@ async function openVaultCache({ key, storage, uid, network }) {
110954
111161
  return;
110955
111162
  }
110956
111163
  const writeKey = new Uint8Array(cacheKey);
111164
+ let body;
110957
111165
  try {
110958
- await storage.write(await sealPayload(writeKey, payload, uid, network));
111166
+ const raw = typeof storage.sealPayload === "function" ? await storage.sealPayload(body = encodePayloadData(payload), payloadCrypto(uid, network, writeKey)) : await sealPayload(writeKey, payload, uid, network);
111167
+ await storage.write(raw);
110959
111168
  } finally {
110960
- cleanBytes(writeKey);
111169
+ cleanBytes(body, writeKey);
110961
111170
  }
110962
111171
  }).catch(() => {});
110963
111172
  return writeTask;
@@ -111760,9 +111969,9 @@ function setErrorStage(error, stage, extra = {}) {
111760
111969
  Object.assign(error, extra);
111761
111970
  return error;
111762
111971
  }
111763
- async function makeChatFileUpload(pair, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
111972
+ async function makeChatFileUpload(chatId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
111764
111973
  try {
111765
- return await makeChatFileUploadPayload(pair, cid, data, {
111974
+ return await makeChatFileUploadPayload(chatId, cid, data, {
111766
111975
  cacheControl
111767
111976
  });
111768
111977
  } catch (error) {
@@ -111772,8 +111981,8 @@ async function makeChatFileUpload(pair, cid, data, { cacheControl = "private, ma
111772
111981
  });
111773
111982
  }
111774
111983
  }
111775
- async function putChatFile(pair, cid, data, options) {
111776
- const upload = await makeChatFileUpload(pair, cid, data, options);
111984
+ async function putChatFile(chatId, cid, data, options) {
111985
+ const upload = await makeChatFileUpload(chatId, cid, data, options);
111777
111986
  try {
111778
111987
  if (typeof options?.uploadChatMedia !== "function") {
111779
111988
  throw new Error("chat media upload required");
@@ -111808,7 +112017,7 @@ async function putSharedFile(data, options = {}) {
111808
112017
  throw setErrorStage(error, "upload", { path: upload.path });
111809
112018
  }
111810
112019
  }
111811
- async function readChatFile(readChatMedia, _pair, file) {
112020
+ async function readChatFile(readChatMedia, file) {
111812
112021
  let body;
111813
112022
  try {
111814
112023
  if (typeof readChatMedia !== "function") {
@@ -111866,8 +112075,8 @@ function buildAttachmentMsg(type, file) {
111866
112075
  return makeAttachment(type, file);
111867
112076
  }
111868
112077
  }
111869
- async function putAttachment(pair, cid, type, data, meta = {}) {
111870
- const file = await putChatFile(pair, cid, data, meta);
112078
+ async function putAttachment(chatId, cid, type, data, meta = {}) {
112079
+ const file = await putChatFile(chatId, cid, data, meta);
111871
112080
  return buildAttachmentMsg(type, {
111872
112081
  ...file,
111873
112082
  ...pickAttachmentMeta(meta)
@@ -111880,26 +112089,26 @@ async function putSharedAttachment(type, data, meta = {}) {
111880
112089
  ...pickAttachmentMeta(meta)
111881
112090
  });
111882
112091
  }
111883
- async function putImg(pair, cid, data, meta = {}) {
111884
- return putAttachment(pair, cid, "img", data, meta);
112092
+ async function putImg(chatId, cid, data, meta = {}) {
112093
+ return putAttachment(chatId, cid, "img", data, meta);
111885
112094
  }
111886
- async function putGif(pair, cid, data, meta = {}) {
111887
- return putAttachment(pair, cid, "gif", data, meta);
112095
+ async function putGif(chatId, cid, data, meta = {}) {
112096
+ return putAttachment(chatId, cid, "gif", data, meta);
111888
112097
  }
111889
- async function putM4a(pair, cid, data, meta = {}) {
111890
- return putAttachment(pair, cid, "m4a", data, meta);
112098
+ async function putM4a(chatId, cid, data, meta = {}) {
112099
+ return putAttachment(chatId, cid, "m4a", data, meta);
111891
112100
  }
111892
- async function putMp4(pair, cid, data, meta = {}) {
111893
- return putAttachment(pair, cid, "mp4", data, meta);
112101
+ async function putMp4(chatId, cid, data, meta = {}) {
112102
+ return putAttachment(chatId, cid, "mp4", data, meta);
111894
112103
  }
111895
- async function putFile(pair, cid, data, meta = {}) {
111896
- return putAttachment(pair, cid, "file", data, meta);
112104
+ async function putFile(chatId, cid, data, meta = {}) {
112105
+ return putAttachment(chatId, cid, "file", data, meta);
111897
112106
  }
111898
- function readMsgAttachment(readChatMedia, pair, msg) {
111899
- return readChatFile(readChatMedia, pair, msg);
112107
+ function readMsgAttachment(readChatMedia, msg) {
112108
+ return readChatFile(readChatMedia, msg);
111900
112109
  }
111901
- function readMsgFile(readChatMedia, pair, msg) {
111902
- return readMsgAttachment(readChatMedia, pair, msg);
112110
+ function readMsgFile(readChatMedia, msg) {
112111
+ return readMsgAttachment(readChatMedia, msg);
111903
112112
  }
111904
112113
  var init_media2 = __esm(() => {
111905
112114
  init_files2();
@@ -112148,14 +112357,6 @@ function ownerEditedPreview(senderPubkey, message) {
112148
112357
  failed: false
112149
112358
  };
112150
112359
  }
112151
- function messageFileChatId(message) {
112152
- try {
112153
- const ref = getMediaFileRef(message?.p);
112154
- return ref?.type === "chat" ? ref.chatId : "";
112155
- } catch {
112156
- return "";
112157
- }
112158
- }
112159
112360
  async function recipientForSend(cloud, receiverChatPK, options, needed) {
112160
112361
  if (!needed) {
112161
112362
  return null;
@@ -112176,50 +112377,51 @@ async function sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, messa
112176
112377
  if (!chatId) {
112177
112378
  throw new Error("chat id required");
112178
112379
  }
112179
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112180
- if (options?.linkId && cleanText(options.linkId) !== pair.linkId) {
112181
- throw new Error("link mismatch");
112182
- }
112183
- const retention = cleanChatRetention(options?.retention ?? options?.ttlMode);
112184
- const tsMs = Date.now();
112185
- const messagePayload = withMessageRetention(message, retention);
112186
- const actionOp = actionOpForPayload(messagePayload);
112187
- const { head, body } = await sealMsg(pair, messagePayload, { op: actionOp, ts: tsMs });
112188
- const ttlMs = actionOp === CHAT_ACTION_OPS.CREATE ? newStorageTtlMs() : null;
112189
- const messageId = head.cid;
112190
- const msgData = {
112191
- head,
112192
- body,
112193
- ttlMs
112194
- };
112195
- const recipientProfile = await recipientForSend(cloud, receiverChatPK, options, updatePreview || options?.ping === true);
112196
- const sentMessage = ownerPreview(senderPubkey, messagePayload, messageId, head, tsMs, ttlMs);
112197
- const preview2 = updatePreview ? sentMessage : null;
112198
- const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112199
- peerUid: recipientProfile?.uid || cleanText(options?.receiverUid),
112200
- peerSigningPublicKey: recipientProfile?.signingPublicKey,
112201
- entry: options?.ownEntry,
112202
- settings: options?.chatSettings,
112203
- preview: preview2,
112204
- ts: tsMs
112205
- }) : null;
112206
- const pingKind = updatePreview ? "message" : cleanText(options?.pingKind) || "ping";
112207
- const ping = recipientProfile?.uid && (updatePreview || options?.ping === true) ? await sealPing(senderPubkey, senderPrivkey, receiverChatPK, {
112208
- kind: pingKind,
112209
- chatId,
112210
- senderUid: cleanText(options?.senderUid),
112211
- messageId,
112212
- ts: tsMs
112213
- }) : null;
112214
- const write = await cloud.chat.messages.send({
112215
- chatId,
112216
- messageId,
112217
- message: msgData,
112218
- ownerEntry,
112219
- inbox: ping ? { recipientUid: recipientProfile?.uid, ping } : null,
112220
- onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
112380
+ return withCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId }, async (pair) => {
112381
+ if (options?.linkId && cleanText(options.linkId) !== pair.linkId) {
112382
+ throw new Error("link mismatch");
112383
+ }
112384
+ const retention = cleanChatRetention(options?.retention ?? options?.ttlMode);
112385
+ const tsMs = Date.now();
112386
+ const messagePayload = withMessageRetention(message, retention);
112387
+ const actionOp = actionOpForPayload(messagePayload);
112388
+ const { head, body } = await sealMsg(pair, messagePayload, { op: actionOp, ts: tsMs });
112389
+ const ttlMs = actionOp === CHAT_ACTION_OPS.CREATE ? newStorageTtlMs() : null;
112390
+ const messageId = head.cid;
112391
+ const msgData = {
112392
+ head,
112393
+ body,
112394
+ ttlMs
112395
+ };
112396
+ const recipientProfile = await recipientForSend(cloud, receiverChatPK, options, updatePreview || options?.ping === true);
112397
+ const sentMessage = ownerPreview(senderPubkey, messagePayload, messageId, head, tsMs, ttlMs);
112398
+ const preview2 = updatePreview ? sentMessage : null;
112399
+ const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112400
+ peerUid: recipientProfile?.uid || cleanText(options?.receiverUid),
112401
+ peerSigningPublicKey: recipientProfile?.signingPublicKey,
112402
+ entry: options?.ownEntry,
112403
+ settings: options?.chatSettings,
112404
+ preview: preview2,
112405
+ ts: tsMs
112406
+ }) : null;
112407
+ const pingKind = updatePreview ? "message" : cleanText(options?.pingKind) || "ping";
112408
+ const ping = recipientProfile?.uid && (updatePreview || options?.ping === true) ? await sealPing(senderPubkey, senderPrivkey, receiverChatPK, {
112409
+ kind: pingKind,
112410
+ chatId,
112411
+ senderUid: cleanText(options?.senderUid),
112412
+ messageId,
112413
+ ts: tsMs
112414
+ }) : null;
112415
+ const write = await cloud.chat.messages.send({
112416
+ chatId,
112417
+ messageId,
112418
+ message: msgData,
112419
+ ownerEntry,
112420
+ inbox: ping ? { recipientUid: recipientProfile?.uid, ping } : null,
112421
+ onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
112422
+ });
112423
+ return { chatId, msgId: messageId, cid: head.cid, message: sentMessage, preview: preview2, ...write?.skipped ? { skipped: true } : {} };
112221
112424
  });
112222
- return { chatId, msgId: messageId, cid: head.cid, message: sentMessage, preview: preview2, ...write?.skipped ? { skipped: true } : {} };
112223
112425
  }
112224
112426
  async function sendReadReceipt(cloud, senderPubkey, senderPrivkey, receiverChatPK, target, options = {}) {
112225
112427
  const receipt = {
@@ -112329,27 +112531,28 @@ async function setChatRetention(cloud, chatId, senderPubkey, senderPrivkey, peer
112329
112531
  throw new Error("vault locked");
112330
112532
  }
112331
112533
  const nextRetention = cleanChatRetention(retention);
112332
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
112333
- if (chatId && pair.chatId !== chatId) {
112334
- throw new Error("chat mismatch");
112335
- }
112336
- const systemMessage = {
112337
- ...makeRetentionSystemMsg(nextRetention),
112338
- cid: cleanText(options?.cid) || makeCid(),
112339
- s: senderPubkey
112340
- };
112341
- await sendMsg(cloud, senderPubkey, senderPrivkey, peerChatPK, systemMessage, {
112342
- chatId,
112343
- linkId: pair.linkId,
112344
- updatePreview: true,
112345
- retention: nextRetention,
112346
- chatExists: true,
112347
- senderUid: options?.senderUid,
112348
- ownEntry: options?.ownEntry,
112349
- chatSettings: { retention: nextRetention },
112350
- onCommitted: options?.onCommitted
112534
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
112535
+ if (chatId && pair.chatId !== chatId) {
112536
+ throw new Error("chat mismatch");
112537
+ }
112538
+ const systemMessage = {
112539
+ ...makeRetentionSystemMsg(nextRetention),
112540
+ cid: cleanText(options?.cid) || makeCid(),
112541
+ s: senderPubkey
112542
+ };
112543
+ await sendMsg(cloud, senderPubkey, senderPrivkey, peerChatPK, systemMessage, {
112544
+ chatId,
112545
+ linkId: pair.linkId,
112546
+ updatePreview: true,
112547
+ retention: nextRetention,
112548
+ chatExists: true,
112549
+ senderUid: options?.senderUid,
112550
+ ownEntry: options?.ownEntry,
112551
+ chatSettings: { retention: nextRetention },
112552
+ onCommitted: options?.onCommitted
112553
+ });
112554
+ return nextRetention;
112351
112555
  });
112352
- return nextRetention;
112353
112556
  }
112354
112557
  async function uploadAttachmentMsg(_cloud, senderPubkey, senderPrivkey, receiverChatPK, attachment = {}) {
112355
112558
  if (!senderPrivkey || !senderPubkey) {
@@ -112363,95 +112566,96 @@ async function uploadAttachmentMsg(_cloud, senderPubkey, senderPrivkey, receiver
112363
112566
  if (!chatId) {
112364
112567
  throw new Error("chat id required");
112365
112568
  }
112366
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112367
112569
  const type = cleanText(attachment?.type) || "file";
112368
112570
  const data = attachment?.data;
112369
112571
  const meta = attachment?.meta || {};
112370
112572
  switch (type) {
112371
112573
  case "img":
112372
- return putImg(pair, nextCid, data, meta);
112574
+ return putImg(chatId, nextCid, data, meta);
112373
112575
  case "gif":
112374
- return putGif(pair, nextCid, data, meta);
112576
+ return putGif(chatId, nextCid, data, meta);
112375
112577
  case "m4a":
112376
- return putM4a(pair, nextCid, data, meta);
112578
+ return putM4a(chatId, nextCid, data, meta);
112377
112579
  case "mp4":
112378
- return putMp4(pair, nextCid, data, meta);
112580
+ return putMp4(chatId, nextCid, data, meta);
112379
112581
  case "file":
112380
- return putFile(pair, nextCid, data, meta);
112582
+ return putFile(chatId, nextCid, data, meta);
112381
112583
  default:
112382
- return putAttachment(pair, nextCid, type, data, meta);
112584
+ return putAttachment(chatId, nextCid, type, data, meta);
112383
112585
  }
112384
112586
  }
112385
112587
  async function updateMsg(cloud, chatId, msgId, senderPubkey, senderPrivkey, receiverChatPK, newMessage, options = {}) {
112386
112588
  if (!senderPubkey || !senderPrivkey)
112387
112589
  throw new Error("vault locked");
112388
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112389
- if (pair.chatId !== chatId) {
112390
- throw new Error("chat mismatch");
112391
- }
112392
- const target = cleanText(newMessage?.cid) || cleanText(msgId);
112393
- if (!target) {
112394
- throw new Error("message target required");
112395
- }
112396
- const op = newMessage?.t === "req" && cleanText(newMessage?.tx) ? CHAT_ACTION_OPS.PAY_CONFIRM : CHAT_ACTION_OPS.EDIT;
112397
- const tsMs = Date.now();
112398
- const action = {
112399
- ...newMessage || {},
112400
- cid: makeCid(),
112401
- s: senderPubkey
112402
- };
112403
- const { head, body } = await sealMsg(pair, action, { op, target, ts: tsMs });
112404
- const messageId = head.cid;
112405
- const updatePreview = options?.updatePreview === true;
112406
- const preview2 = updatePreview ? ownerEditedPreview(senderPubkey, newMessage) : null;
112407
- const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112408
- peerUid: cleanText(options?.receiverUid),
112409
- peerSigningPublicKey: cleanText(options?.peerSigningPublicKey),
112410
- entry: options?.ownEntry,
112411
- preview: preview2,
112412
- ts: preview2?.ts ?? tsMs
112413
- }) : null;
112414
- await cloud.chat.messages.send({
112415
- chatId,
112416
- messageId,
112417
- message: {
112418
- head,
112419
- body,
112420
- ttlMs: null
112421
- },
112422
- ownerEntry,
112423
- inbox: null
112590
+ return withCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId }, async (pair) => {
112591
+ if (pair.chatId !== chatId) {
112592
+ throw new Error("chat mismatch");
112593
+ }
112594
+ const target = cleanText(newMessage?.cid) || cleanText(msgId);
112595
+ if (!target) {
112596
+ throw new Error("message target required");
112597
+ }
112598
+ const op = newMessage?.t === "req" && cleanText(newMessage?.tx) ? CHAT_ACTION_OPS.PAY_CONFIRM : CHAT_ACTION_OPS.EDIT;
112599
+ const tsMs = Date.now();
112600
+ const action = {
112601
+ ...newMessage || {},
112602
+ cid: makeCid(),
112603
+ s: senderPubkey
112604
+ };
112605
+ const { head, body } = await sealMsg(pair, action, { op, target, ts: tsMs });
112606
+ const messageId = head.cid;
112607
+ const updatePreview = options?.updatePreview === true;
112608
+ const preview2 = updatePreview ? ownerEditedPreview(senderPubkey, newMessage) : null;
112609
+ const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112610
+ peerUid: cleanText(options?.receiverUid),
112611
+ peerSigningPublicKey: cleanText(options?.peerSigningPublicKey),
112612
+ entry: options?.ownEntry,
112613
+ preview: preview2,
112614
+ ts: preview2?.ts ?? tsMs
112615
+ }) : null;
112616
+ await cloud.chat.messages.send({
112617
+ chatId,
112618
+ messageId,
112619
+ message: {
112620
+ head,
112621
+ body,
112622
+ ttlMs: null
112623
+ },
112624
+ ownerEntry,
112625
+ inbox: null
112626
+ });
112627
+ return {
112628
+ chatId,
112629
+ msgId: messageId,
112630
+ cid: head.cid,
112631
+ preview: preview2
112632
+ };
112424
112633
  });
112425
- return {
112426
- chatId,
112427
- msgId: messageId,
112428
- cid: head.cid,
112429
- preview: preview2
112430
- };
112431
112634
  }
112432
112635
  async function deleteMsg(cloud, chatId, messageOrId, senderPubkey, senderPrivkey, peerChatPK, options = {}) {
112433
112636
  if (!cloud || !chatId || !messageOrId || !senderPubkey || !senderPrivkey || !peerChatPK) {
112434
112637
  return false;
112435
112638
  }
112436
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
112437
- if (pair.chatId !== chatId) {
112438
- return false;
112439
- }
112440
- const item = messageDeleteItems([messageOrId])[0];
112441
- const target = cleanText(options?.docId) || cleanText(item?.id);
112442
- if (!target || target.startsWith("local:")) {
112443
- return false;
112444
- }
112445
- const deleteTarget = cleanText(options?.target) || cleanText(getMessageKey(messageOrId)) || target;
112446
- await sendDeleteAction(cloud, senderPubkey, senderPrivkey, peerChatPK, deleteTarget, {
112447
- chatId,
112448
- linkId: options?.linkId,
112449
- senderUid: options?.senderUid
112450
- });
112451
- await cloud.chat.messages.delete(chatId, target, {
112452
- mediaPaths: item?.mediaPath ? [item.mediaPath] : []
112639
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
112640
+ if (pair.chatId !== chatId) {
112641
+ return false;
112642
+ }
112643
+ const item = messageDeleteItems([messageOrId])[0];
112644
+ const target = cleanText(options?.docId) || cleanText(item?.id);
112645
+ if (!target || target.startsWith("local:")) {
112646
+ return false;
112647
+ }
112648
+ const deleteTarget = cleanText(options?.target) || cleanText(getMessageKey(messageOrId)) || target;
112649
+ await sendDeleteAction(cloud, senderPubkey, senderPrivkey, peerChatPK, deleteTarget, {
112650
+ chatId,
112651
+ linkId: options?.linkId,
112652
+ senderUid: options?.senderUid
112653
+ });
112654
+ await cloud.chat.messages.delete(chatId, target, {
112655
+ mediaPaths: item?.mediaPath ? [item.mediaPath] : []
112656
+ });
112657
+ return true;
112453
112658
  });
112454
- return true;
112455
112659
  }
112456
112660
  async function deleteMsgs(cloud, chatId, messages, senderPubkey, senderPrivkey, peerChatPK) {
112457
112661
  if (!cloud || !chatId || !senderPubkey || !senderPrivkey || !peerChatPK) {
@@ -112461,31 +112665,31 @@ async function deleteMsgs(cloud, chatId, messages, senderPubkey, senderPrivkey,
112461
112665
  if (!items.length) {
112462
112666
  return 0;
112463
112667
  }
112464
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
112465
- if (pair.chatId !== chatId) {
112466
- return 0;
112467
- }
112468
- let deleted = 0;
112469
- for (let index = 0;index < items.length; index += DELETE_WRITE_BATCH_SIZE) {
112470
- const chunk = items.slice(index, index + DELETE_WRITE_BATCH_SIZE);
112471
- const targets = chunk.map((item) => cleanText(item.id)).filter((target) => target && !target.startsWith("local:"));
112472
- const mediaPaths = chunk.map((item) => cleanText(item.mediaPath)).filter(Boolean);
112473
- deleted += await cloud.chat.messages.deleteMany(chatId, targets, { mediaPaths });
112474
- }
112475
- return deleted;
112668
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
112669
+ if (pair.chatId !== chatId) {
112670
+ return 0;
112671
+ }
112672
+ let deleted = 0;
112673
+ for (let index = 0;index < items.length; index += DELETE_WRITE_BATCH_SIZE) {
112674
+ const chunk = items.slice(index, index + DELETE_WRITE_BATCH_SIZE);
112675
+ const targets = chunk.map((item) => cleanText(item.id)).filter((target) => target && !target.startsWith("local:"));
112676
+ const mediaPaths = chunk.map((item) => cleanText(item.mediaPath)).filter(Boolean);
112677
+ deleted += await cloud.chat.messages.deleteMany(chatId, targets, { mediaPaths });
112678
+ }
112679
+ return deleted;
112680
+ });
112476
112681
  }
112477
- async function readMsgMedia(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg) {
112478
- return readMsgAttachment2(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg);
112682
+ async function readMsgMedia(readChatMedia, msg) {
112683
+ return readMsgAttachment2(readChatMedia, msg);
112479
112684
  }
112480
- async function readMsgAttachment2(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg) {
112685
+ async function readMsgAttachment2(readChatMedia, msg) {
112481
112686
  if (typeof readChatMedia !== "function") {
112482
112687
  throw new Error("chat media read required");
112483
112688
  }
112484
- if (!userChatPK || !userPrivKey || !peerChatPK || !msg) {
112689
+ if (!msg) {
112485
112690
  return null;
112486
112691
  }
112487
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: msg?.chatId || messageFileChatId(msg) });
112488
- return readMsgFile(readChatMedia, pair, msg);
112692
+ return readMsgFile(readChatMedia, msg);
112489
112693
  }
112490
112694
  var DELETE_WRITE_BATCH_SIZE;
112491
112695
  var init_write = __esm(() => {
@@ -112728,14 +112932,14 @@ function hasInvalidStoredMediaRef(message) {
112728
112932
  return true;
112729
112933
  }
112730
112934
  }
112731
- function readMessageMedia(cloud, media, chatPK, chatPrivateKey, peerChatPK, message) {
112935
+ function readMessageMedia(cloud, media, message) {
112732
112936
  const readChatMedia = cloud?.chat?.media?.read;
112733
112937
  if (typeof media?.readMessageFile === "function") {
112734
- return media.readMessageFile(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
112938
+ return media.readMessageFile(readChatMedia, message);
112735
112939
  }
112736
- return readMsgMedia(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
112940
+ return readMsgMedia(readChatMedia, message);
112737
112941
  }
112738
- function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey, localCache }) {
112942
+ function createChatSave({ cloud, media = {}, chatBanned, localCache }) {
112739
112943
  const makeMessagePermanent = async (chatId, message) => {
112740
112944
  if (chatBanned) {
112741
112945
  throw makeChatUnavailableError();
@@ -112753,7 +112957,7 @@ function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey,
112753
112957
  const updated = await makeMsgTemporary(cloud, chatId, list, ttl);
112754
112958
  return { updated, ttl };
112755
112959
  };
112756
- const readMessageFile = async (peerChatPK, message) => {
112960
+ const readMessageFile = async (_peerChatPK, message) => {
112757
112961
  if (chatBanned) {
112758
112962
  throw makeChatUnavailableError();
112759
112963
  }
@@ -112775,7 +112979,7 @@ function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey,
112775
112979
  throw makeFileGoneError();
112776
112980
  }
112777
112981
  try {
112778
- const bytes = await readMessageMedia(cloud, media, chatPK, chatPrivateKey, peerChatPK, message);
112982
+ const bytes = await readMessageMedia(cloud, media, message);
112779
112983
  saveMedia(localCache, message, bytes, message);
112780
112984
  return bytes;
112781
112985
  } catch (error) {
@@ -113421,8 +113625,11 @@ function withSharedMediaUpload(cloud, media, meta = {}) {
113421
113625
  };
113422
113626
  }
113423
113627
  function uploadMessageAttachment(cloud, media, senderPubkey, senderPrivkey, receiverChatPK, attachment = {}) {
113628
+ if (!senderPubkey || !senderPrivkey) {
113629
+ throw new Error("vault locked");
113630
+ }
113424
113631
  if (typeof media?.uploadAttachment === "function") {
113425
- return media.uploadAttachment(senderPubkey, senderPrivkey, receiverChatPK, attachment);
113632
+ return media.uploadAttachment(attachment);
113426
113633
  }
113427
113634
  return uploadAttachmentMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, withMediaUpload(cloud, media, attachment));
113428
113635
  }
@@ -113720,17 +113927,21 @@ ${cid}` : "";
113720
113927
  const requiredPreview = previewRequired || sendOptions.chatExists !== true;
113721
113928
  let local = await showLocalMessage(peerChatPK, message, sendOptions);
113722
113929
  let committed = false;
113930
+ let sent = false;
113931
+ const markSent = () => {
113932
+ if (sent)
113933
+ return;
113934
+ sent = true;
113935
+ sentChatIdsRef.current.add(local.chatId);
113936
+ markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
113937
+ };
113723
113938
  const markCommitted = () => {
113724
113939
  if (committed)
113725
113940
  return;
113726
113941
  committed = true;
113727
- sentChatIdsRef.current.add(local.chatId);
113942
+ markSent();
113728
113943
  markDone(diag, "chat.send.commit", startedAt, { type: local?.t || "txt", previewRequired: !!requiredPreview });
113729
113944
  };
113730
- const markSent = () => {
113731
- sentChatIdsRef.current.add(local.chatId);
113732
- markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
113733
- };
113734
113945
  return await new Promise((resolve, reject) => {
113735
113946
  const job = {
113736
113947
  previewKey: local.chatId,
@@ -113948,7 +114159,8 @@ ${cid}` : "";
113948
114159
  linkId: local.linkId,
113949
114160
  version: local.version,
113950
114161
  sendOptions,
113951
- nextAttachment: prepared.nextAttachment
114162
+ nextAttachment: prepared.nextAttachment,
114163
+ committed: false
113952
114164
  };
113953
114165
  }));
113954
114166
  return await new Promise((resolve, reject) => {
@@ -113957,7 +114169,9 @@ ${cid}` : "";
113957
114169
  reject,
113958
114170
  onError: () => {
113959
114171
  for (const item of locals) {
113960
- markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114172
+ if (!item.committed) {
114173
+ markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114174
+ }
113961
114175
  }
113962
114176
  },
113963
114177
  run: async () => {
@@ -113972,6 +114186,7 @@ ${cid}` : "";
113972
114186
  results.push({ peerChatPK: item.peerChatPK, ok: false, error: uploadError });
113973
114187
  continue;
113974
114188
  }
114189
+ let sentMessage;
113975
114190
  try {
113976
114191
  let uploaded = uploads.get(uploadKey);
113977
114192
  if (!uploaded) {
@@ -113984,17 +114199,32 @@ ${cid}` : "";
113984
114199
  cid: item.cid,
113985
114200
  s: chatPK
113986
114201
  };
113987
- const sentMessage = withMessageRetention(sent, item.sendOptions.retention);
114202
+ sentMessage = withMessageRetention(sent, item.sendOptions.retention);
113988
114203
  rememberCachedLocalMedia(item.chatId, item.cid, sentMessage);
113989
- await sendMsg(cloud, chatPK, chatPrivateKey, item.peerChatPK, sentMessage, { ...item.sendOptions, chatId: item.chatId, linkId: item.linkId, senderUid: uid });
113990
- markLocalStatus(item.chatId, item.cid, LOCAL_SENT);
114204
+ await sendMsg(cloud, chatPK, chatPrivateKey, item.peerChatPK, sentMessage, {
114205
+ ...item.sendOptions,
114206
+ chatId: item.chatId,
114207
+ linkId: item.linkId,
114208
+ senderUid: uid,
114209
+ onCommitted: () => {
114210
+ if (item.committed)
114211
+ return;
114212
+ item.committed = true;
114213
+ sentChatIdsRef.current.add(item.chatId);
114214
+ markLocalStatus(item.chatId, item.cid, LOCAL_SENT);
114215
+ }
114216
+ });
113991
114217
  results.push({ peerChatPK: item.peerChatPK, ok: true, message: sentMessage });
113992
114218
  } catch (error) {
113993
114219
  if (!uploads.has(uploadKey)) {
113994
114220
  uploadErrors.set(uploadKey, error);
113995
114221
  }
113996
- markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
113997
- results.push({ peerChatPK: item.peerChatPK, ok: false, error });
114222
+ if (item.committed) {
114223
+ results.push({ peerChatPK: item.peerChatPK, ok: true, message: sentMessage });
114224
+ } else {
114225
+ markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114226
+ results.push({ peerChatPK: item.peerChatPK, ok: false, error });
114227
+ }
113998
114228
  }
113999
114229
  }
114000
114230
  return results;
@@ -114801,7 +115031,6 @@ async function decryptRecordEntries(records, userChatPK, userPrivKey, peerChatPK
114801
115031
  return [];
114802
115032
  }
114803
115033
  const signingKeysVersion = signingKeysFingerprint(options?.signingKeysByChatKey);
114804
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId });
114805
115034
  const entryOptions = { ...options, signingKeysVersion };
114806
115035
  const shouldYield = positiveInt(options?.yieldEvery, 0) > 0;
114807
115036
  const entries = new Array(source.length).fill(null);
@@ -114818,38 +115047,40 @@ async function decryptRecordEntries(records, userChatPK, userPrivKey, peerChatPK
114818
115047
  }
114819
115048
  pending.push({ record, index });
114820
115049
  };
114821
- const openPending = async (pending) => {
114822
- if (!pending.length) {
114823
- return;
114824
- }
114825
- const opened = await decryptRecordBatchWithPair(pending.map((item) => item.record), pair, cache, entryOptions);
114826
- for (let index = 0;index < opened.length; index += 1) {
114827
- const entry = opened[index];
114828
- if (entry) {
114829
- entries[pending[index].index] = entry;
115050
+ return withCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId }, async (pair) => {
115051
+ const openPending = async (pending) => {
115052
+ if (!pending.length) {
115053
+ return;
115054
+ }
115055
+ const opened = await decryptRecordBatchWithPair(pending.map((item) => item.record), pair, cache, entryOptions);
115056
+ for (let index = 0;index < opened.length; index += 1) {
115057
+ const entry = opened[index];
115058
+ if (entry) {
115059
+ entries[pending[index].index] = entry;
115060
+ }
115061
+ }
115062
+ };
115063
+ if (!options?.signal && !shouldYield) {
115064
+ const pending = [];
115065
+ for (let index = 0;index < source.length; index += 1) {
115066
+ collectRecord(source[index], index, pending);
114830
115067
  }
115068
+ await openPending(pending);
115069
+ return entries.filter(Boolean);
114831
115070
  }
114832
- };
114833
- if (!options?.signal && !shouldYield) {
114834
- const pending = [];
114835
- for (let index = 0;index < source.length; index += 1) {
114836
- collectRecord(source[index], index, pending);
115071
+ const chunkSize = positiveInt(options?.yieldEvery, source.length || 1);
115072
+ for (let index = 0;index < source.length; index += chunkSize) {
115073
+ throwIfAborted(options?.signal);
115074
+ const chunk = source.slice(index, index + chunkSize);
115075
+ const pending = [];
115076
+ for (let offset = 0;offset < chunk.length; offset += 1) {
115077
+ collectRecord(chunk[offset], index + offset, pending);
115078
+ }
115079
+ await openPending(pending);
115080
+ await maybeYield(index + chunk.length, options);
114837
115081
  }
114838
- await openPending(pending);
114839
115082
  return entries.filter(Boolean);
114840
- }
114841
- const chunkSize = positiveInt(options?.yieldEvery, source.length || 1);
114842
- for (let index = 0;index < source.length; index += chunkSize) {
114843
- throwIfAborted(options?.signal);
114844
- const chunk = source.slice(index, index + chunkSize);
114845
- const pending = [];
114846
- for (let offset = 0;offset < chunk.length; offset += 1) {
114847
- collectRecord(chunk[offset], index + offset, pending);
114848
- }
114849
- await openPending(pending);
114850
- await maybeYield(index + chunk.length, options);
114851
- }
114852
- return entries.filter(Boolean);
115083
+ });
114853
115084
  }
114854
115085
  function messageQueryLimit(pageSize) {
114855
115086
  return positiveInt(pageSize, MSG_BATCH_SIZE);
@@ -115195,9 +115426,10 @@ async function decryptMsg(msgData, userChatPK, userPrivKey, peerChatPK, options
115195
115426
  return null;
115196
115427
  }
115197
115428
  try {
115198
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId });
115199
- const message = await openMsg(pair, msgData, { signingKeysByChatKey: options?.signingKeysByChatKey, crypto: options?.crypto });
115200
- return normalizeDecryptedMsg(msgData, message);
115429
+ return await withCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId }, async (pair) => {
115430
+ const message = await openMsg(pair, msgData, { signingKeysByChatKey: options?.signingKeysByChatKey, crypto: options?.crypto });
115431
+ return normalizeDecryptedMsg(msgData, message);
115432
+ });
115201
115433
  } catch {
115202
115434
  return null;
115203
115435
  }
@@ -116267,12 +116499,12 @@ function warmKeyStore(ref) {
116267
116499
  }
116268
116500
  return ref.current;
116269
116501
  }
116270
- function readMessageMedia2(cloud, media, chatPK, chatPrivateKey, peerChatPK, message) {
116502
+ function readMessageMedia2(cloud, media, message) {
116271
116503
  const readChatMedia = cloud?.chat?.media?.read;
116272
116504
  if (typeof media?.readMessageFile === "function") {
116273
- return media.readMessageFile(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
116505
+ return media.readMessageFile(readChatMedia, message);
116274
116506
  }
116275
- return readMsgMedia(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
116507
+ return readMsgMedia(readChatMedia, message);
116276
116508
  }
116277
116509
  function isDenied3(error) {
116278
116510
  return error?.code === "permission-denied";
@@ -116479,12 +116711,12 @@ function createChatMessageBatches({ cloud, media = {}, chatPK, chatPrivateKey, c
116479
116711
  return;
116480
116712
  }
116481
116713
  const tasks = buildMediaTasks();
116482
- const readFile = async (peerChatPK, message) => {
116714
+ const readFile = async (_peerChatPK, message) => {
116483
116715
  const cached = await readCachedMedia(localCache, message);
116484
116716
  if (cached?.byteLength) {
116485
116717
  return cached;
116486
116718
  }
116487
- const bytes = await readMessageMedia2(cloud, media, chatPK, chatPrivateKey, peerChatPK, message);
116719
+ const bytes = await readMessageMedia2(cloud, media, message);
116488
116720
  if (bytes?.byteLength) {
116489
116721
  saveMedia(localCache, message, bytes, message);
116490
116722
  }
@@ -117231,6 +117463,16 @@ function pingSigningKeys(ping, existing) {
117231
117463
  [ping.payload.senderChatPK]: ping.payload.signingPublicKey
117232
117464
  };
117233
117465
  }
117466
+ function chatSettingsFromPreview(settings, preview2) {
117467
+ const current = normalizeChatSettings(settings);
117468
+ if (!isSystemMsg(preview2)) {
117469
+ return current;
117470
+ }
117471
+ return {
117472
+ ...current,
117473
+ retention: cleanChatRetention(preview2.retention)
117474
+ };
117475
+ }
117234
117476
  function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options = {}) {
117235
117477
  const ts = timestampMs(ms, null) ?? timestampMs(existing?.ts, null) ?? timestampMs(preview2?.ts, null) ?? 0;
117236
117478
  if (!ts) {
@@ -117247,7 +117489,7 @@ function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options
117247
117489
  peerChatPK: ping.payload.senderChatPK,
117248
117490
  peerUid: existing?.peerUid || cleanText(ping.payload.senderUid) || null,
117249
117491
  signingKeysByChatKey: pingSigningKeys(ping, existing),
117250
- settings: existing?.settings,
117492
+ settings: chatSettingsFromPreview(existing?.settings, preview2),
117251
117493
  saved: existing?.saved || null,
117252
117494
  readMs,
117253
117495
  startMs,
@@ -117306,7 +117548,7 @@ async function savePing(cloud, uid, userChatPK, userPrivKey, ping, options = {})
117306
117548
  peerUid: peerUid2 || existing?.peerUid,
117307
117549
  peerSigningPublicKey: ping.payload.signingPublicKey || existing?.signingKeysByChatKey?.[ping.payload.senderChatPK],
117308
117550
  signingKeysByChatKey,
117309
- settings: existing?.settings,
117551
+ settings: chatSettingsFromPreview(existing?.settings, preview2),
117310
117552
  preview: preview2 || existing?.preview,
117311
117553
  readMs: existing?.readMs,
117312
117554
  startMs: existing ? existing.startMs : timestampMs(options.startPreview?.ts ?? preview2?.ts, null, { positive: true })
@@ -117495,6 +117737,7 @@ var init_inbox = __esm(() => {
117495
117737
  init_ping();
117496
117738
  init_query();
117497
117739
  init_chats();
117740
+ init_ttl();
117498
117741
  init_time();
117499
117742
  init_protocol();
117500
117743
  });
@@ -117990,6 +118233,7 @@ function createChatList({
117990
118233
  let chats = [];
117991
118234
  let peers = [];
117992
118235
  let isChatDataReady = false;
118236
+ let isChatListLive = false;
117993
118237
  let chatSourceReady = false;
117994
118238
  let lastChat = null;
117995
118239
  let serverChatIds = [];
@@ -118026,6 +118270,7 @@ function createChatList({
118026
118270
  chats,
118027
118271
  peers,
118028
118272
  isChatDataReady,
118273
+ isChatListLive,
118029
118274
  hasChats: chats.length > 0,
118030
118275
  previewNow,
118031
118276
  lastChat,
@@ -118059,6 +118304,13 @@ function createChatList({
118059
118304
  isChatDataReady = value;
118060
118305
  publish();
118061
118306
  };
118307
+ const setIsChatListLive = (next) => {
118308
+ const value = nextValue(isChatListLive, next);
118309
+ if (Object.is(isChatListLive, value))
118310
+ return;
118311
+ isChatListLive = value;
118312
+ publish();
118313
+ };
118062
118314
  const setLastChat = (next) => {
118063
118315
  const value = nextValue(lastChat, next);
118064
118316
  if (Object.is(lastChat, value))
@@ -118268,6 +118520,7 @@ function createChatList({
118268
118520
  const resetChatList = (ready = false) => {
118269
118521
  chatSourceReady = ready;
118270
118522
  syncChatDataReady();
118523
+ setIsChatListLive(false);
118271
118524
  setChats((prev) => prev.length ? [] : prev);
118272
118525
  setPeers((prev) => prev.length ? [] : prev);
118273
118526
  setLastChat((prev) => prev ? null : prev);
@@ -118357,6 +118610,7 @@ function createChatList({
118357
118610
  resetAllChatState();
118358
118611
  return;
118359
118612
  }
118613
+ setIsChatListLive(false);
118360
118614
  let cancelled = false;
118361
118615
  const hydrateKey = localCache?.id ? `${localCache.id}:${chatPK}` : "";
118362
118616
  if (hydrateKey && lastHydratedCacheKeyRef.current !== hydrateKey) {
@@ -118456,6 +118710,7 @@ function createChatList({
118456
118710
  writeCache: true,
118457
118711
  serverEndReached: cachedHistoryEndRef.current
118458
118712
  });
118713
+ setIsChatListLive(true);
118459
118714
  listenUpdateSeqRef.current += 1;
118460
118715
  markDiag(diag, "chat.provider.listen.update", {
118461
118716
  seq: listenUpdateSeqRef.current,
@@ -118498,6 +118753,7 @@ function createChatList({
118498
118753
  markChatSourceReady();
118499
118754
  fillVisibleChatPage();
118500
118755
  }, (error) => {
118756
+ setIsChatListLive(false);
118501
118757
  markError(diag, "chat.provider.listen", listenStartedAt, error);
118502
118758
  if (shouldResetChatListAfterListenError(error)) {
118503
118759
  resetAllChatState();
@@ -122187,11 +122443,6 @@ function createWalletPrivacy({ wallet, ghostWallet, diag }) {
122187
122443
  const startedAt = Date.now();
122188
122444
  const desired = desiredPrivacy;
122189
122445
  markDiag(diag, "wallet.privacy.start", { ghostWallet: desired });
122190
- const current = typeof wallet.getWalletSettings === "function" ? await wallet.getWalletSettings() : null;
122191
- if (closed || requestRevision !== revision || current?.privateEnabled === true === desired) {
122192
- markDone(diag, "wallet.privacy", startedAt, { changed: false, cancelled: closed || requestRevision !== revision });
122193
- return;
122194
- }
122195
122446
  await wallet.setPrivacyEnabled(desired);
122196
122447
  markDone(diag, "wallet.privacy", startedAt, { changed: true });
122197
122448
  }).catch((error) => {
@@ -123041,7 +123292,7 @@ function adaptiveClaimBatchSize(maxBatchSize, queueDepth) {
123041
123292
  }
123042
123293
  return 1;
123043
123294
  }
123044
- function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, transferStore, appState = null, deferBackgroundWork = null, backgroundSignal = null, diag }) {
123295
+ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, eagerCacheHistory = true, transferStore, appState = null, deferBackgroundWork = null, backgroundSignal = null, diag }) {
123045
123296
  if (!transferStore || typeof transferStore.subscribe !== "function" || typeof transferStore.getSnapshot !== "function" || typeof transferStore.getCacheState !== "function" || typeof transferStore.setHistory !== "function") {
123046
123297
  throw new Error("createWalletTransferSession requires transferStore");
123047
123298
  }
@@ -123055,6 +123306,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123055
123306
  const initialTransfers = canUseInitialStore ? transferStore.getTransfers() : [];
123056
123307
  const initialHistoryTransfers = canUseInitialStore ? transferStore.getHistoryTransfers() : [];
123057
123308
  const initialTxReady = canUseInitialStore && (initialStoreSnapshot.ready || initialTransfers.length > 0 || initialHistoryTransfers.length > 0);
123309
+ const initialCacheReady = initialTxReady && (eagerCacheHistory === false || initialStoreSnapshot.serverEndReached === true && initialHistoryTransfers.length >= initialStoreSnapshot.historyCount);
123058
123310
  const listeners = new Set;
123059
123311
  let isTxLoading = false;
123060
123312
  let publishedSnapshot = null;
@@ -124771,6 +125023,9 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124771
125023
  if (!wallet || !localCache || !walletPKRef.current) {
124772
125024
  return;
124773
125025
  }
125026
+ if (initialCacheReady) {
125027
+ return;
125028
+ }
124774
125029
  const hydrateKey = `${localCache.id || "cache"}:${walletPKRef.current}`;
124775
125030
  if (cacheHydrateKeyRef.current === hydrateKey) {
124776
125031
  return;
@@ -124795,7 +125050,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124795
125050
  return;
124796
125051
  }
124797
125052
  const hydratedHead = applyCachedTransferState(cachedHead, { label: "wallet.provider.cache.hydrate", publish: true, startedAt: hydrateStartedAt });
124798
- if (cancelled)
125053
+ if (cancelled || !eagerCacheHistory)
124799
125054
  return;
124800
125055
  await loadFullTransferCache({ label: "wallet.provider.cache.fullHydrate", publish: !hydratedHead });
124801
125056
  }).catch((error) => {
@@ -125204,6 +125459,7 @@ function createWalletSession({
125204
125459
  pendingClaimBatchSize = null,
125205
125460
  activeTransferRefreshMs = null,
125206
125461
  eagerOutgoingStatusRefresh = false,
125462
+ eagerCacheHistory = true,
125207
125463
  sentTransferRefreshDelayMs = null,
125208
125464
  deferBackgroundWork = null,
125209
125465
  appState = null,
@@ -125232,6 +125488,7 @@ function createWalletSession({
125232
125488
  claimIncomingTransfers,
125233
125489
  pendingClaimBatchSize,
125234
125490
  eagerOutgoingStatusRefresh,
125491
+ eagerCacheHistory,
125235
125492
  transferStore,
125236
125493
  appState,
125237
125494
  deferBackgroundWork,
@@ -125342,7 +125599,7 @@ function createWalletSession({
125342
125599
  balanceReady: balanceState.balanceReady,
125343
125600
  isBalanceLoading: balanceState.isBalanceLoading,
125344
125601
  isWalletDataReady: true,
125345
- isWalletDataLoaded: balanceState.balanceReady && currentTransferState.txReady,
125602
+ isWalletDataLoaded: balanceState.balanceReady || currentTransferState.txReady,
125346
125603
  refresh: claims.refresh,
125347
125604
  requestTransferDiscoveryHotWindow: polling.requestTransferDiscoveryHotWindow,
125348
125605
  claimDeposits: claims.claimDeposits,
@@ -169284,7 +169541,12 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169284
169541
  "Content-Type": upload?.metadata?.contentType || "application/octet-stream"
169285
169542
  };
169286
169543
  if (typeof uploadSignedStorageBytes === "function") {
169287
- await uploadSignedStorageBytes(url, upload?.body, { ...signed, method, headers, metadata: upload?.metadata || {}, path: upload?.path || "" });
169544
+ await uploadSignedStorageBytes(url, upload?.body, { ...signed, method, headers, metadata: upload?.metadata || {}, path: upload?.path || "" }).catch((error) => {
169545
+ if (error?.status === 412 && (headers["X-Goog-If-Generation-Match"] === "0" || headers["x-goog-if-generation-match"] === "0")) {
169546
+ return true;
169547
+ }
169548
+ throw error;
169549
+ });
169288
169550
  return true;
169289
169551
  }
169290
169552
  const payload = typeof Blob !== "undefined" && upload?.body instanceof Blob ? upload.body : toBytes(upload?.body, "upload bytes");
@@ -169293,6 +169555,9 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169293
169555
  headers,
169294
169556
  body: payload
169295
169557
  });
169558
+ if (response.status === 412 && (headers["X-Goog-If-Generation-Match"] === "0" || headers["x-goog-if-generation-match"] === "0")) {
169559
+ return true;
169560
+ }
169296
169561
  if (!response.ok) {
169297
169562
  const error = new Error(`signed upload failed (${response.status || 0})`);
169298
169563
  error.status = response.status || 0;
@@ -169615,16 +169880,11 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169615
169880
  await deleteObject(ref(targetStorage, avatarPath(uid)));
169616
169881
  return true;
169617
169882
  }
169618
- async function writeProfileWalletPK(walletPK, { network } = {}) {
169619
- if (!walletPK)
169620
- throw new Error("wallet public key required");
169621
- await callVaultFunction("setWalletPK", VAULT_SIGNATURE_PURPOSES.SET_WALLET_IDENTITY, { walletPK, network });
169622
- return true;
169623
- }
169624
- async function writeProfileChatPK(chatPK) {
169625
- if (!chatPK)
169626
- throw new Error("chat public key required");
169627
- await callVaultFunction("setChatPK", VAULT_SIGNATURE_PURPOSES.SET_CHAT_IDENTITY, { chatPK });
169883
+ async function writeProfileIdentity(identity) {
169884
+ if (!identity || typeof identity !== "object" || Array.isArray(identity)) {
169885
+ throw new Error("profile identity required");
169886
+ }
169887
+ await callVaultFunction("setIdentities", VAULT_SIGNATURE_PURPOSES.SET_IDENTITIES, identity);
169628
169888
  return true;
169629
169889
  }
169630
169890
  async function getUsername(username) {
@@ -169674,9 +169934,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169674
169934
  }
169675
169935
  return openSettings(key, uid, readCloudBytes(saved, "settings body"));
169676
169936
  }
169677
- const settings = normalizeSettings(defaultSettings);
169678
- await callVaultFunction("setSettings", VAULT_SIGNATURE_PURPOSES.SET_SETTINGS, { settings: bytesBase64(await sealSettings(key, uid, settings)) });
169679
- return settings;
169937
+ return normalizeSettings(defaultSettings);
169680
169938
  }
169681
169939
  async function writeSettings(uid, settings, { currentSettings, key } = {}) {
169682
169940
  requireUid(uid);
@@ -169814,20 +170072,12 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169814
170072
  const snapshot = await getDocs(roleQuery);
169815
170073
  return peerRecordsFromSnapshot(snapshot);
169816
170074
  }
169817
- async function uploadChatMedia(upload) {
170075
+ async function uploadMedia(upload) {
169818
170076
  const path = upload?.path;
169819
170077
  if (!path)
169820
170078
  throw new Error("media path required");
169821
- getChatMediaFileRef(path);
169822
- await uploadStorageFile(path, upload?.body, upload?.metadata || {});
169823
- return true;
169824
- }
169825
- async function uploadSharedMedia(upload) {
169826
- const path = upload?.path;
169827
- if (!path)
169828
- throw new Error("shared media path required");
169829
- const signed = await callFunction("reserveSharedMediaUpload", {
169830
- sharedId: upload?.sharedId,
170079
+ getMediaFileRef(path);
170080
+ const signed = await callFunction("reserveMediaUpload", {
169831
170081
  path,
169832
170082
  size: uploadByteSize(upload?.body),
169833
170083
  contentType: upload?.metadata?.contentType || "application/octet-stream"
@@ -169835,6 +170085,14 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169835
170085
  await uploadSignedStorageFile(upload, signed?.upload);
169836
170086
  return true;
169837
170087
  }
170088
+ async function uploadChatMedia(upload) {
170089
+ getChatMediaFileRef(upload?.path);
170090
+ return uploadMedia(upload);
170091
+ }
170092
+ async function uploadSharedMedia(upload) {
170093
+ getSharedMediaFileRef(upload?.path);
170094
+ return uploadMedia(upload);
170095
+ }
169838
170096
  async function readChatMedia(path) {
169839
170097
  if (!path)
169840
170098
  throw new Error("media path required");
@@ -170564,16 +170822,13 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170564
170822
  },
170565
170823
  profile: {
170566
170824
  watch: watchProfile,
170825
+ identity: {
170826
+ write: writeProfileIdentity
170827
+ },
170567
170828
  avatar: {
170568
170829
  write: writeProfileAvatar,
170569
170830
  upload: uploadProfileAvatar,
170570
170831
  delete: deleteProfileAvatar
170571
- },
170572
- walletpk: {
170573
- write: writeProfileWalletPK
170574
- },
170575
- chatpk: {
170576
- write: writeProfileChatPK
170577
170832
  }
170578
170833
  },
170579
170834
  private: {
@@ -173325,7 +173580,7 @@ function createRuntimeProductActions({
173325
173580
  if (Object.prototype.hasOwnProperty.call(patch, "walletNetwork") && nextNetwork !== previousNetwork) {
173326
173581
  await lockVault();
173327
173582
  if (nextNetwork)
173328
- setNetwork(nextNetwork);
173583
+ await setNetwork(nextNetwork);
173329
173584
  }
173330
173585
  return { ...next, autolock: { ...next.autolock }, locked: nextNetwork !== previousNetwork };
173331
173586
  }
@@ -173954,7 +174209,8 @@ function createRuntimeChatActions({ getRuntime, getSession, resolvePeer }) {
173954
174209
  const updated = await target.runtime.chat.getSnapshot().updateMessage(target.chat.id, target.message.id, message, target.profile.chatPK);
173955
174210
  return {
173956
174211
  chat: simplifyAccountChat(target.chat, target.profile),
173957
- message: simplifyAccountMessage(updated || message, target.chat, target.session.chatPK, options2)
174212
+ message: simplifyAccountMessage(message, target.chat, target.session.chatPK, options2),
174213
+ actionId: cleanText(updated?.msgId || updated?.cid) || null
173958
174214
  };
173959
174215
  }
173960
174216
  async function deleteChat(peer, options2 = {}) {
@@ -174865,7 +175121,7 @@ var package_default;
174865
175121
  var init_package = __esm(() => {
174866
175122
  package_default = {
174867
175123
  name: "veyl",
174868
- version: "0.31.1",
175124
+ version: "0.35.0",
174869
175125
  private: true,
174870
175126
  license: "Apache-2.0",
174871
175127
  workspaces: {
@@ -174881,16 +175137,16 @@ var init_package = __esm(() => {
174881
175137
  "@noble/curves": "^2.2.0",
174882
175138
  "@noble/hashes": "^2.2.0",
174883
175139
  "@scure/bip39": "^2.2.0",
174884
- "@tailwindcss/postcss": "^4.3.2",
175140
+ "@tailwindcss/postcss": "^4.3.3",
174885
175141
  bech32: "^2.0.0",
174886
175142
  firebase: "^12.16.0",
174887
175143
  "lucide-react": "^1.24.0",
174888
175144
  next: "16.3.0-preview.6",
174889
175145
  "next-themes": "^0.4.6",
174890
- postcss: "^8.5.19",
174891
- react: "19.2.7",
174892
- "react-dom": "19.2.7",
174893
- tailwindcss: "^4.3.2",
175146
+ postcss: "^8.5.22",
175147
+ react: "19.2.8",
175148
+ "react-dom": "19.2.8",
175149
+ tailwindcss: "^4.3.3",
174894
175150
  "tw-animate-css": "^1.4.0"
174895
175151
  }
174896
175152
  },
@@ -175165,9 +175421,17 @@ class VeylHeadlessClient {
175165
175421
  resolvePeer: (peer, resolveOptions) => this.resolvePeer(peer, resolveOptions),
175166
175422
  chat: this.runtimeChat,
175167
175423
  lockVault: () => this.lockVault(),
175168
- setNetwork: (network) => {
175169
- this.network = network;
175170
- this.userOwner.setNetwork(network);
175424
+ setNetwork: async (network) => {
175425
+ const nextNetwork = String(network || "").toUpperCase();
175426
+ this.network = nextNetwork;
175427
+ this.userOwner.setNetwork(nextNetwork);
175428
+ if (this.profileData) {
175429
+ await this.saveProfile({
175430
+ ...this.profileData,
175431
+ network: nextNetwork,
175432
+ walletPK: null
175433
+ });
175434
+ }
175171
175435
  },
175172
175436
  runPasskeyBrowserFlow,
175173
175437
  webUrl: options2.webUrl,
@@ -175280,7 +175544,7 @@ class VeylHeadlessClient {
175280
175544
  username: profile.username,
175281
175545
  profile: profile.profile,
175282
175546
  credentialId: profile.credentialId,
175283
- network: profile.network || this.network,
175547
+ network: this.session?.network || profile.network || this.network,
175284
175548
  hasVault: profile.hasVault === true,
175285
175549
  hasVaultSecret: !!profile.vaultSecret,
175286
175550
  walletPK: profile.walletPK || null,
@@ -175641,10 +175905,9 @@ class VeylHeadlessClient {
175641
175905
  vaultCrypto: headlessVaultCrypto,
175642
175906
  bootWallet: (walletMnemonic, currentUser, { network }) => bootWallet(walletMnemonic, currentUser, {
175643
175907
  SparkWallet: SparkWalletNodeJS,
175644
- cloud: this.cloud,
175645
175908
  network
175646
175909
  }),
175647
- bootChat: (chatSeed, currentUser) => bootChat(chatSeed, currentUser, { cloud: this.cloud }),
175910
+ bootChat: (chatSeed, currentUser) => bootChat(chatSeed, currentUser),
175648
175911
  openLocalCache: (cacheSeed, { uid, network }) => openHeadlessLocalDataCache(cacheSeed, {
175649
175912
  homeDir: this.store.dir,
175650
175913
  uid,
@@ -175655,6 +175918,7 @@ class VeylHeadlessClient {
175655
175918
  this.userOwner.setNetwork(network);
175656
175919
  }
175657
175920
  });
175921
+ await opened.walletReady;
175658
175922
  this.userSnapshot = this.userOwner.getSnapshot();
175659
175923
  } catch (error) {
175660
175924
  if (opened)
@@ -175689,6 +175953,7 @@ class VeylHeadlessClient {
175689
175953
  ...profile,
175690
175954
  hasVault: true,
175691
175955
  vaultSecret: options2.saveSecret === false ? null : vaultSecret,
175956
+ network: session.network,
175692
175957
  walletPK: session.walletPK,
175693
175958
  chatPK: session.chatPK,
175694
175959
  vaultCreatedAt: Date.now()
@@ -175733,6 +175998,7 @@ class VeylHeadlessClient {
175733
175998
  ...profile,
175734
175999
  hasVault: true,
175735
176000
  vaultSecret: options2.saveSecret === false ? profile.vaultSecret || null : profile.vaultSecret || vaultSecret,
176001
+ network: session.network,
175736
176002
  walletPK: session.walletPK,
175737
176003
  chatPK: session.chatPK,
175738
176004
  lastUnlockedAt: Date.now()