@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/cli.js CHANGED
@@ -118,9 +118,10 @@ var init_links = __esm(() => {
118
118
  });
119
119
 
120
120
  // ../../shared/community.js
121
- var COMMUNITY_RULES_VERSION = "2026-05-11.1", COMMUNITY_SECTIONS;
121
+ var COMMUNITY_RULES_VERSION = "2026-05-11.1", COMMUNITY_SUPPORT_EMAIL, COMMUNITY_SECTIONS;
122
122
  var init_community = __esm(() => {
123
123
  init_links();
124
+ COMMUNITY_SUPPORT_EMAIL = links.contact.replace("mailto:", "");
124
125
  COMMUNITY_SECTIONS = [
125
126
  {
126
127
  title: "Use veyl directly and lawfully",
@@ -160,7 +161,7 @@ var init_community = __esm(() => {
160
161
  {
161
162
  title: "Reporting and support",
162
163
  body: [
163
- `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.`,
164
+ `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.`,
164
165
  "Because veyl uses end-to-end encryption, we may rely on reports, account-level signals, and evidence you submit when reviewing abuse."
165
166
  ]
166
167
  }
@@ -979,7 +980,7 @@ var package_default;
979
980
  var init_package = __esm(() => {
980
981
  package_default = {
981
982
  name: "veyl",
982
- version: "0.31.1",
983
+ version: "0.35.0",
983
984
  private: true,
984
985
  license: "Apache-2.0",
985
986
  workspaces: {
@@ -995,16 +996,16 @@ var init_package = __esm(() => {
995
996
  "@noble/curves": "^2.2.0",
996
997
  "@noble/hashes": "^2.2.0",
997
998
  "@scure/bip39": "^2.2.0",
998
- "@tailwindcss/postcss": "^4.3.2",
999
+ "@tailwindcss/postcss": "^4.3.3",
999
1000
  bech32: "^2.0.0",
1000
1001
  firebase: "^12.16.0",
1001
1002
  "lucide-react": "^1.24.0",
1002
1003
  next: "16.3.0-preview.6",
1003
1004
  "next-themes": "^0.4.6",
1004
- postcss: "^8.5.19",
1005
- react: "19.2.7",
1006
- "react-dom": "19.2.7",
1007
- tailwindcss: "^4.3.2",
1005
+ postcss: "^8.5.22",
1006
+ react: "19.2.8",
1007
+ "react-dom": "19.2.8",
1008
+ tailwindcss: "^4.3.3",
1008
1009
  "tw-animate-css": "^1.4.0"
1009
1010
  }
1010
1011
  },
@@ -104224,9 +104225,8 @@ var init_vaultsignature = __esm(() => {
104224
104225
  DELETE_ACCOUNT: "delete-account",
104225
104226
  DELETE_PASSKEY: "delete-passkey",
104226
104227
  LOGOUT_ALL_DEVICES: "logout-all-devices",
104227
- SET_CHAT_IDENTITY: "set-chat-identity",
104228
- SET_SETTINGS: "set-settings",
104229
- SET_WALLET_IDENTITY: "set-wallet-identity"
104228
+ SET_IDENTITIES: "set-identities",
104229
+ SET_SETTINGS: "set-settings"
104230
104230
  });
104231
104231
  });
104232
104232
 
@@ -104535,7 +104535,7 @@ var init_seed = __esm(() => {
104535
104535
  });
104536
104536
 
104537
104537
  // ../../shared/config.js
104538
- 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;
104538
+ 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;
104539
104539
  var init_config = __esm(() => {
104540
104540
  MINUTE_MS = 60 * MS_PER_SECOND;
104541
104541
  HOUR_MS = 60 * MINUTE_MS;
@@ -105981,20 +105981,17 @@ function expectedSigningKeys(pair, signingKeysByChatKey) {
105981
105981
  }
105982
105982
  return values;
105983
105983
  }
105984
- async function verifyExpectedSignatureBeforeOpen(pair, sig, bytes, options = {}) {
105984
+ async function preverifyExpectedSigner(pair, sig, bytes, options = {}) {
105985
105985
  if (!options?.crypto?.preverifyBeforeOpen) {
105986
- return true;
105987
- }
105988
- const signingKeysByChatKey = expectedSigningKeys(pair, options?.signingKeysByChatKey);
105989
- if (signingKeysByChatKey.length < 2) {
105990
- return true;
105986
+ return "";
105991
105987
  }
105992
- for (const signingPublicKey of signingKeysByChatKey) {
105988
+ const signingPublicKeys = expectedSigningKeys(pair, options?.signingKeysByChatKey);
105989
+ for (const signingPublicKey of signingPublicKeys) {
105993
105990
  if (await verifyMsgSignature(signingPublicKey, sig, bytes, options)) {
105994
- return true;
105991
+ return signingPublicKey;
105995
105992
  }
105996
105993
  }
105997
- return false;
105994
+ return null;
105998
105995
  }
105999
105996
  async function sealMsg(pair, message, options = {}) {
106000
105997
  const head = getHead(pair, message);
@@ -106015,14 +106012,16 @@ async function openMsg(pair, data, options = {}) {
106015
106012
  const { suite, flags, nonce, sig, ct } = unpackMsgBody(data.body);
106016
106013
  const aad = getMsgAad(pair, head, suite, flags);
106017
106014
  const signatureInput = getSignatureInput(aad, nonce, ct);
106018
- if (!await verifyExpectedSignatureBeforeOpen(pair, sig, signatureInput, options)) {
106015
+ const preverifiedSigner = await preverifyExpectedSigner(pair, sig, signatureInput, options);
106016
+ if (preverifiedSigner == null) {
106019
106017
  throw new Error("invalid chat action signature");
106020
106018
  }
106021
106019
  const plaintext = await openMsgPlaintext(pair, nonce, ct, aad, options);
106022
106020
  try {
106023
106021
  const action = JSON.parse(decoder.decode(plaintext));
106024
106022
  const normalized = normalizeOpenedAction(pair, head, action, options);
106025
- if (!await verifyMsgSignature(normalized.signingPublicKey, sig, signatureInput, options)) {
106023
+ const validSignature = preverifiedSigner ? normalized.signingPublicKey === preverifiedSigner : await verifyMsgSignature(normalized.signingPublicKey, sig, signatureInput, options);
106024
+ if (!validSignature) {
106026
106025
  throw new Error("invalid chat action signature");
106027
106026
  }
106028
106027
  return {
@@ -106096,40 +106095,120 @@ var init_protocol = __esm(() => {
106096
106095
  });
106097
106096
 
106098
106097
  // ../../shared/chat/pairs.js
106098
+ function accountGeneration(chatPK) {
106099
+ return accountGenerations.get(cleanText(chatPK)) || 0;
106100
+ }
106099
106101
  function getChatPairKey(chatPK, peerChatPK, chatId = "") {
106100
106102
  if (!chatPK || !peerChatPK)
106101
106103
  return null;
106102
106104
  return `${cleanText(chatPK)}|${cleanText(peerChatPK)}|${cleanText(chatId)}`;
106103
106105
  }
106104
- function clearChatPairCache() {
106105
- for (const pair of pairCache.values()) {
106106
- closeChatPair(pair);
106106
+ function closeEntry(entry) {
106107
+ if (!entry || entry.closed || entry.leases > 0) {
106108
+ return;
106109
+ }
106110
+ entry.closed = true;
106111
+ closeChatPair(entry.pair);
106112
+ }
106113
+ function retireEntry(entry) {
106114
+ if (!entry || entry.retired) {
106115
+ return;
106116
+ }
106117
+ entry.retired = true;
106118
+ closeEntry(entry);
106119
+ }
106120
+ function releaseEntry(entry) {
106121
+ if (!entry || entry.leases <= 0) {
106122
+ return;
106123
+ }
106124
+ entry.leases -= 1;
106125
+ if (entry.retired) {
106126
+ closeEntry(entry);
106107
106127
  }
106108
- pairCache.clear();
106109
106128
  }
106110
- async function getCachedPair(chatPK, chatPrivKey, peerChatPK, options = {}) {
106129
+ function evictOverflow() {
106130
+ while (pairCache.size > MAX_PAIR_CACHE) {
106131
+ const firstKey = pairCache.keys().next().value;
106132
+ if (!firstKey) {
106133
+ return;
106134
+ }
106135
+ const entry = pairCache.get(firstKey);
106136
+ pairCache.delete(firstKey);
106137
+ retireEntry(entry);
106138
+ }
106139
+ }
106140
+ function clearChatPairCache(chatPK = "") {
106141
+ const account = cleanText(chatPK);
106142
+ if (!account) {
106143
+ cacheGeneration += 1;
106144
+ accountGenerations.clear();
106145
+ for (const entry of pairCache.values()) {
106146
+ retireEntry(entry);
106147
+ }
106148
+ pairCache.clear();
106149
+ return;
106150
+ }
106151
+ accountGenerations.set(account, accountGeneration(account) + 1);
106152
+ for (const [key, entry] of pairCache) {
106153
+ if (entry.account !== account) {
106154
+ continue;
106155
+ }
106156
+ pairCache.delete(key);
106157
+ retireEntry(entry);
106158
+ }
106159
+ }
106160
+ async function acquireCachedPair(chatPK, chatPrivKey, peerChatPK, options = {}) {
106161
+ const account = cleanText(chatPK);
106111
106162
  const chatId = cleanText(options?.chatId);
106112
106163
  const key = getChatPairKey(chatPK, peerChatPK, chatId);
106164
+ const generation = cacheGeneration;
106165
+ const accountVersion = accountGeneration(account);
106113
106166
  if (!key) {
106114
- return openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
106167
+ const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
106168
+ const entry2 = { pair, leases: 1, retired: true, closed: false };
106169
+ return {
106170
+ pair,
106171
+ release: () => releaseEntry(entry2)
106172
+ };
106115
106173
  }
106116
- const cached = pairCache.get(key);
106117
- if (cached)
106118
- return cached;
106119
- const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
106120
- pairCache.set(key, pair);
106121
- if (pairCache.size > MAX_PAIR_CACHE) {
106122
- const firstKey = pairCache.keys().next().value;
106123
- if (firstKey) {
106124
- closeChatPair(pairCache.get(firstKey));
106125
- pairCache.delete(firstKey);
106174
+ let entry = pairCache.get(key);
106175
+ if (!entry) {
106176
+ const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
106177
+ if (generation !== cacheGeneration || accountVersion !== accountGeneration(account)) {
106178
+ const staleEntry = { pair, leases: 1, retired: true, closed: false };
106179
+ return {
106180
+ pair,
106181
+ release: () => releaseEntry(staleEntry)
106182
+ };
106126
106183
  }
106184
+ entry = pairCache.get(key);
106185
+ if (entry) {
106186
+ closeChatPair(pair);
106187
+ } else {
106188
+ entry = { account, pair, leases: 0, retired: false, closed: false };
106189
+ pairCache.set(key, entry);
106190
+ evictOverflow();
106191
+ }
106192
+ } else {
106193
+ pairCache.delete(key);
106194
+ pairCache.set(key, entry);
106195
+ }
106196
+ entry.leases += 1;
106197
+ return {
106198
+ pair: entry.pair,
106199
+ release: () => releaseEntry(entry)
106200
+ };
106201
+ }
106202
+ async function withCachedPair(chatPK, chatPrivKey, peerChatPK, options, runWithPair) {
106203
+ const { pair, release } = await acquireCachedPair(chatPK, chatPrivKey, peerChatPK, options);
106204
+ try {
106205
+ return await runWithPair(pair);
106206
+ } finally {
106207
+ release();
106127
106208
  }
106128
- return pair;
106129
106209
  }
106130
106210
  async function resolveLinkId(chatPK, chatPrivKey, peerChatPK) {
106131
- const pair = await getCachedPair(chatPK, chatPrivKey, peerChatPK);
106132
- return pair?.linkId || null;
106211
+ return withCachedPair(chatPK, chatPrivKey, peerChatPK, {}, (pair) => pair?.linkId || null);
106133
106212
  }
106134
106213
  async function resolvePeerChat(cloud, chatPK, chatPrivateKey, peerChatPK, options = {}) {
106135
106214
  const existingChatId = cleanText(options?.chatId);
@@ -106160,13 +106239,14 @@ async function resolvePeerChat(cloud, chatPK, chatPrivateKey, peerChatPK, option
106160
106239
  exists: linkChat?.exists === true
106161
106240
  };
106162
106241
  }
106163
- var pairCache, MAX_PAIR_CACHE;
106242
+ var pairCache, MAX_PAIR_CACHE, cacheGeneration = 0, accountGenerations;
106164
106243
  var init_pairs = __esm(() => {
106165
106244
  init_config();
106166
106245
  init_chat();
106167
106246
  init_protocol();
106168
106247
  pairCache = new Map;
106169
106248
  MAX_PAIR_CACHE = CHAT_PAIR_CACHE_LIMIT;
106249
+ accountGenerations = new Map;
106170
106250
  });
106171
106251
 
106172
106252
  // ../../shared/utils/diagnostics.js
@@ -106235,11 +106315,9 @@ function getBootWalletClass(SparkWallet3, { enableTokenSync = false } = {}) {
106235
106315
  function bytesToHex6(bytes) {
106236
106316
  return Array.from(bytes || []).map((b) => b.toString(16).padStart(2, "0")).join("");
106237
106317
  }
106238
- async function bootWallet(walletMnemonic, user, { SparkWallet: SparkWallet3, cloud, network, enableTokenSync = false, diag = null } = {}) {
106318
+ async function bootWallet(walletMnemonic, user, { SparkWallet: SparkWallet3, network, enableTokenSync = false, diag = null } = {}) {
106239
106319
  if (!SparkWallet3)
106240
106320
  throw new Error("SparkWallet missing");
106241
- if (!cloud)
106242
- throw new Error("cloud missing");
106243
106321
  if (!network)
106244
106322
  throw new Error("network missing");
106245
106323
  const startedAt = Date.now();
@@ -106274,53 +106352,41 @@ async function bootWallet(walletMnemonic, user, { SparkWallet: SparkWallet3, clo
106274
106352
  const walletPK = lowerText(idPk);
106275
106353
  const expectedWalletPK = resolveWalletPK(user, network);
106276
106354
  markDone(diag, "vault.bootWallet.identity", identityStartedAt);
106277
- const setupStartedAt = Date.now();
106355
+ let walletPKNeedsWrite = false;
106278
106356
  if (!expectedWalletPK) {
106279
- markDiag(diag, "vault.bootWallet.setup.start", { reason: "missing-wallet-pk" });
106280
- await cloud.user.profile.walletpk.write(idPk, { network });
106281
- markDone(diag, "vault.bootWallet.setup", setupStartedAt, { wrote: true });
106357
+ walletPKNeedsWrite = true;
106282
106358
  } else if (!sameText(expectedWalletPK, walletPK)) {
106283
106359
  throw new Error("wallet identity mismatch for account");
106284
106360
  } else if (!hasWalletPKForNetwork(user, network)) {
106285
- markDiag(diag, "vault.bootWallet.setup.start", { reason: "missing-network-wallet-pk" });
106286
- await cloud.user.profile.walletpk.write(idPk, { network });
106287
- markDone(diag, "vault.bootWallet.setup", setupStartedAt, { wrote: true });
106288
- } else {
106289
- markDiag(diag, "vault.bootWallet.setup.skip", { elapsedMs: Date.now() - setupStartedAt });
106361
+ walletPKNeedsWrite = true;
106290
106362
  }
106291
106363
  markDone(diag, "vault.bootWallet", startedAt);
106292
- return { wallet, walletPK };
106364
+ return { wallet, walletPK, walletPKNeedsWrite };
106293
106365
  } catch (error) {
106294
106366
  markError(diag, "vault.bootWallet", startedAt, error);
106295
106367
  throw error;
106296
106368
  }
106297
106369
  }
106298
- async function bootChat(chatSeed, user, { cloud, diag = null } = {}) {
106370
+ async function bootChat(chatSeed, user, { diag = null } = {}) {
106299
106371
  const startedAt = Date.now();
106300
106372
  markDiag(diag, "vault.bootChat.start", { hasChatPK: !!user?.chatPK });
106301
106373
  try {
106302
- if (!cloud)
106303
- throw new Error("cloud missing");
106304
106374
  const keyStartedAt = Date.now();
106305
106375
  const chatKeyPair = getKeyPair(chatSeed);
106306
106376
  chatSeed.fill(0);
106307
106377
  const chatPKHex = bytesToHex6(chatKeyPair.pub);
106308
106378
  markDone(diag, "vault.bootChat.derive", keyStartedAt);
106379
+ let chatPKNeedsWrite = false;
106309
106380
  if (!user.chatPK) {
106310
- const setupStartedAt = Date.now();
106311
- markDiag(diag, "vault.bootChat.setup.start", { reason: "missing-chat-pk" });
106312
- await cloud.user.profile.chatpk.write(chatPKHex);
106313
- markDone(diag, "vault.bootChat.setup", setupStartedAt, { wrote: true });
106381
+ chatPKNeedsWrite = true;
106314
106382
  } else if (!sameText(user.chatPK, chatPKHex)) {
106315
106383
  chatKeyPair.priv.fill(0);
106316
106384
  chatKeyPair.pub.fill(0);
106317
106385
  throw new Error("chat identity mismatch for account");
106318
- } else {
106319
- markDiag(diag, "vault.bootChat.setup.skip", {});
106320
106386
  }
106321
106387
  chatKeyPair.pub.fill(0);
106322
106388
  markDone(diag, "vault.bootChat", startedAt);
106323
- return { chatPrivateKey: chatKeyPair.priv, chatPK: chatPKHex };
106389
+ return { chatPrivateKey: chatKeyPair.priv, chatPK: chatPKHex, chatPKNeedsWrite };
106324
106390
  } catch (error) {
106325
106391
  markError(diag, "vault.bootChat", startedAt, error);
106326
106392
  chatSeed.fill(0);
@@ -106333,14 +106399,16 @@ function lockWallet(wallet) {
106333
106399
  cleanup?.catch?.(() => {});
106334
106400
  } catch {}
106335
106401
  }
106336
- function lockChat(chatPrivateKey) {
106402
+ function lockChat(chatPrivateKey, chatPK = "") {
106337
106403
  try {
106338
106404
  if (chatPrivateKey) {
106339
106405
  chatPrivateKey.fill(0);
106340
106406
  }
106341
106407
  } catch {}
106342
106408
  try {
106343
- clearChatPairCache();
106409
+ if (chatPK) {
106410
+ clearChatPairCache(chatPK);
106411
+ }
106344
106412
  } catch {}
106345
106413
  }
106346
106414
  var init_vault = __esm(() => {
@@ -106360,7 +106428,7 @@ function walletPubkey(value) {
106360
106428
  }
106361
106429
  return key;
106362
106430
  }
106363
- function closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner } = {}) {
106431
+ function closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner } = {}) {
106364
106432
  try {
106365
106433
  vaultAccess?.close?.();
106366
106434
  } catch {}
@@ -106371,7 +106439,7 @@ function closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess
106371
106439
  localCache?.close?.();
106372
106440
  } catch {}
106373
106441
  lockWallet(wallet);
106374
- lockChat(chatPrivateKey);
106442
+ lockChat(chatPrivateKey, chatPK);
106375
106443
  }
106376
106444
  function createAccountSession(resources = {}) {
106377
106445
  let closed = false;
@@ -106462,6 +106530,32 @@ function notifySeedDecrypted(callback, markDiag2, source) {
106462
106530
  failed(error);
106463
106531
  }
106464
106532
  }
106533
+ function startWalletBoot(bootWallet2, walletMnemonic, user, options) {
106534
+ let closed = false;
106535
+ let handedOff = false;
106536
+ let wallet = null;
106537
+ const result = Promise.resolve().then(() => bootWallet2(walletMnemonic, user, options)).then((identity) => {
106538
+ wallet = identity?.wallet || null;
106539
+ if (closed && wallet) {
106540
+ lockWallet(wallet);
106541
+ }
106542
+ return identity;
106543
+ });
106544
+ return {
106545
+ result,
106546
+ handoff() {
106547
+ handedOff = true;
106548
+ },
106549
+ close() {
106550
+ if (closed)
106551
+ return;
106552
+ closed = true;
106553
+ if (wallet && !handedOff) {
106554
+ lockWallet(wallet);
106555
+ }
106556
+ }
106557
+ };
106558
+ }
106465
106559
  async function openVaultAccountSession(vault, password, options = {}) {
106466
106560
  const {
106467
106561
  uid,
@@ -106503,6 +106597,7 @@ async function openVaultAccountSession(vault, password, options = {}) {
106503
106597
  throw new Error("unlockSettings missing");
106504
106598
  const { decryptSeed: decryptSeed2, unpackVaultSeedData } = vaultCrypto;
106505
106599
  let wallet = null;
106600
+ let chatPK = "";
106506
106601
  let chatPrivateKey = null;
106507
106602
  let localCache = null;
106508
106603
  let masterSeed = null;
@@ -106513,14 +106608,24 @@ async function openVaultAccountSession(vault, password, options = {}) {
106513
106608
  let vaultSigner = null;
106514
106609
  let vaultAccess = null;
106515
106610
  let vaultAccessResult = null;
106611
+ let walletBoot = null;
106516
106612
  let network;
106517
106613
  try {
106518
106614
  onStage?.("unlocking");
106519
106615
  const authStartedAt = Date.now();
106520
106616
  mark(diag, "vault.unlock.session.start", { source });
106521
- await cloud.auth.user.getIdToken(true);
106522
- mark(diag, "vault.unlock.session.done", { elapsedMs: Date.now() - authStartedAt, source });
106523
- requireCurrent(isCurrent);
106617
+ const authResult = cloud.auth.user.getIdToken(true).then(() => {
106618
+ mark(diag, "vault.unlock.session.done", { elapsedMs: Date.now() - authStartedAt, source });
106619
+ return { error: null };
106620
+ }, (error) => {
106621
+ mark(diag, "vault.unlock.session.error", {
106622
+ elapsedMs: Date.now() - authStartedAt,
106623
+ source,
106624
+ code: error?.code || "",
106625
+ message: error?.message || String(error)
106626
+ });
106627
+ return { error };
106628
+ });
106524
106629
  const unpackStartedAt = Date.now();
106525
106630
  const unpacked = unpackVaultSeedData(vault);
106526
106631
  const { salt, iv, ct, kdf, registry: registryEnvelope } = unpacked;
@@ -106549,6 +106654,9 @@ async function openVaultAccountSession(vault, password, options = {}) {
106549
106654
  cleanBytes(walletEntropy, masterSeed);
106550
106655
  walletEntropy = null;
106551
106656
  masterSeed = null;
106657
+ const sessionResult = await authResult;
106658
+ if (sessionResult.error)
106659
+ throw sessionResult.error;
106552
106660
  requireCurrent(isCurrent);
106553
106661
  const vaultSignatureStartedAt = Date.now();
106554
106662
  mark(diag, "vault.unlock.signature.start", { source });
@@ -106569,17 +106677,13 @@ async function openVaultAccountSession(vault, password, options = {}) {
106569
106677
  onStage?.("wallet");
106570
106678
  const walletStartedAt = Date.now();
106571
106679
  mark(diag, "vault.unlock.wallet.start", { source });
106572
- const walletIdentity = await bootWallet2(walletMnemonic, user, { network });
106573
- wallet = walletIdentity?.wallet;
106574
- const walletPK = walletPubkey(walletIdentity?.walletPK);
106575
- mark(diag, "vault.unlock.wallet.done", { elapsedMs: Date.now() - walletStartedAt, source });
106576
- requireCurrent(isCurrent);
106680
+ walletBoot = startWalletBoot(bootWallet2, walletMnemonic, user, { network, diag });
106577
106681
  onStage?.("chat");
106578
106682
  const chatStartedAt = Date.now();
106579
106683
  mark(diag, "vault.unlock.chat.start", { source });
106580
- const chatIdentity = await bootChat2(chatSeed, user);
106684
+ const chatIdentity = await bootChat2(chatSeed, user, { diag });
106581
106685
  chatPrivateKey = chatIdentity?.chatPrivateKey;
106582
- const chatPK = cleanText(chatIdentity?.chatPK);
106686
+ chatPK = cleanText(chatIdentity?.chatPK);
106583
106687
  if (!chatPrivateKey || !chatPK)
106584
106688
  throw new Error("chat identity missing");
106585
106689
  chatSeed = null;
@@ -106597,11 +106701,11 @@ async function openVaultAccountSession(vault, password, options = {}) {
106597
106701
  throw signatureResult.error;
106598
106702
  vaultAccess = signatureResult.access;
106599
106703
  requireCurrent(isCurrent);
106600
- return createAccountSession({
106704
+ const session = createAccountSession({
106601
106705
  uid,
106602
106706
  network,
106603
- wallet,
106604
- walletPK,
106707
+ wallet: null,
106708
+ walletPK: null,
106605
106709
  chatPK,
106606
106710
  chatPrivateKey,
106607
106711
  localCache,
@@ -106609,12 +106713,56 @@ async function openVaultAccountSession(vault, password, options = {}) {
106609
106713
  vaultAccess,
106610
106714
  vaultPK: vaultSigner.publicKey
106611
106715
  });
106716
+ session.own(walletBoot);
106717
+ const walletReady = walletBoot.result.then(async (walletIdentity) => {
106718
+ if (session.closed)
106719
+ throw new Error("account session closed");
106720
+ const readyWallet = walletIdentity?.wallet;
106721
+ const walletPK = walletPubkey(walletIdentity?.walletPK);
106722
+ if (!readyWallet)
106723
+ throw new Error("wallet missing");
106724
+ mark(diag, "vault.unlock.wallet.done", { elapsedMs: Date.now() - walletStartedAt, source });
106725
+ const identityPayload = {};
106726
+ if (walletIdentity?.walletPKNeedsWrite) {
106727
+ identityPayload.walletPK = walletPK;
106728
+ identityPayload.network = network;
106729
+ }
106730
+ if (chatIdentity?.chatPKNeedsWrite) {
106731
+ identityPayload.chatPK = chatPK;
106732
+ }
106733
+ const identityFields = Object.keys(identityPayload);
106734
+ if (identityFields.length) {
106735
+ const identitiesStartedAt = Date.now();
106736
+ mark(diag, "vault.unlock.identities.start", { source, fields: identityFields });
106737
+ await cloud.user.profile.identity.write(identityPayload);
106738
+ mark(diag, "vault.unlock.identities.done", {
106739
+ elapsedMs: Date.now() - identitiesStartedAt,
106740
+ source,
106741
+ fields: identityFields
106742
+ });
106743
+ } else {
106744
+ mark(diag, "vault.unlock.identities.skip", { source });
106745
+ }
106746
+ if (session.closed)
106747
+ throw new Error("account session closed");
106748
+ walletBoot.handoff();
106749
+ session.wallet = readyWallet;
106750
+ session.walletPK = walletPK;
106751
+ return { wallet: readyWallet, walletPK };
106752
+ }).catch((error) => {
106753
+ walletBoot.close();
106754
+ throw error;
106755
+ });
106756
+ walletReady.catch(() => {});
106757
+ session.walletReady = walletReady;
106758
+ return session;
106612
106759
  } catch (error) {
106760
+ walletBoot?.close();
106613
106761
  if (vaultAccessResult && !vaultAccess) {
106614
106762
  const signatureResult = await vaultAccessResult;
106615
106763
  vaultAccess = signatureResult.access;
106616
106764
  }
106617
- closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner });
106765
+ closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner });
106618
106766
  throw error;
106619
106767
  } finally {
106620
106768
  cleanBytes(masterSeed, walletEntropy, chatSeed, cacheKey, settingsKey);
@@ -106629,6 +106777,7 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
106629
106777
  }
106630
106778
  const walletMnemonic = mnemonicFromWalletEntropy(walletEntropy);
106631
106779
  let wallet = null;
106780
+ let chatPK = "";
106632
106781
  let chatPrivateKey = null;
106633
106782
  let chatPubKey = null;
106634
106783
  let vaultSigner = null;
@@ -106657,10 +106806,11 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
106657
106806
  throw new Error("vault signature uid missing");
106658
106807
  vaultAccess = await cloud.auth.vaultSignature.open(vaultSigner);
106659
106808
  }
106809
+ chatPK = toHex(chatPubKey);
106660
106810
  return createAccountSession({
106661
106811
  wallet,
106662
106812
  walletPK,
106663
- chatPK: toHex(chatPubKey),
106813
+ chatPK,
106664
106814
  chatPrivateKey,
106665
106815
  localCache,
106666
106816
  network,
@@ -106669,7 +106819,7 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
106669
106819
  vaultPK: vaultSigner.publicKey
106670
106820
  });
106671
106821
  } catch (error) {
106672
- closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner });
106822
+ closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner });
106673
106823
  throw error;
106674
106824
  } finally {
106675
106825
  cleanBytes(chatSeed, chatPubKey);
@@ -106980,6 +107130,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
106980
107130
  let authSession = 0;
106981
107131
  let userUid = null;
106982
107132
  let settingsKey = null;
107133
+ let settingsStored = null;
106983
107134
  let avatarFetch = { uid: null, key: null, promise: null };
106984
107135
  let blockedSource = state.blocked;
106985
107136
  let blockedSet = new Set(blockedSource);
@@ -107199,6 +107350,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107199
107350
  userUid = null;
107200
107351
  clearSettingsKey(settingsKey);
107201
107352
  settingsKey = null;
107353
+ settingsStored = null;
107202
107354
  if (signedOutUid) {
107203
107355
  keepOnlyCachedAvatar(null);
107204
107356
  }
@@ -107212,6 +107364,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107212
107364
  if (authUidChanged) {
107213
107365
  clearSettingsKey(settingsKey);
107214
107366
  settingsKey = null;
107367
+ settingsStored = null;
107215
107368
  }
107216
107369
  setState((user) => authUidChanged ? { ...defaultUser, authReady: true, uid: authUser.uid } : { ...user, authReady: true, isAdmin: false, adminReady: false });
107217
107370
  readCachedAvatar(authUser.uid).then((cached) => {
@@ -107268,6 +107421,9 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107268
107421
  fromCache: !!info.fromCache,
107269
107422
  pending: !!info.pending
107270
107423
  });
107424
+ if (!info.fromCache) {
107425
+ settingsStored = Object.prototype.hasOwnProperty.call(privateData, "settings");
107426
+ }
107271
107427
  setState((user) => ({
107272
107428
  ...user,
107273
107429
  agreement: privateData.agreement ?? null,
@@ -107275,6 +107431,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107275
107431
  }));
107276
107432
  }), current((error) => {
107277
107433
  markError(diag, "user.settings.snapshot", authStartedAt, error);
107434
+ settingsStored = null;
107278
107435
  setState((user) => ({
107279
107436
  ...user,
107280
107437
  settingsReady: true,
@@ -107378,6 +107535,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107378
107535
  }
107379
107536
  clearSettingsKey(settingsKey);
107380
107537
  settingsKey = null;
107538
+ settingsStored = null;
107381
107539
  userUid = null;
107382
107540
  avatarFetch = { uid: null, key: null, promise: null };
107383
107541
  state = defaultUser;
@@ -107428,7 +107586,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107428
107586
  throw new Error("auth");
107429
107587
  if (!key)
107430
107588
  throw new Error("settings key required");
107431
- const nextSettings = await cloud.user.settings.read(uid, key);
107589
+ const nextSettings = settingsStored === false ? settingsState() : await cloud.user.settings.read(uid, key);
107432
107590
  if (!isCurrentUser(uid, session))
107433
107591
  return nextSettings;
107434
107592
  setSettingsKey(key);
@@ -107452,6 +107610,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107452
107610
  const nextSettings = await cloud.user.settings.write(uid, patch, { currentSettings: state.settings, key: settingsKey });
107453
107611
  if (!isCurrentUser(uid, session))
107454
107612
  return nextSettings;
107613
+ settingsStored = true;
107455
107614
  setState((user) => ({
107456
107615
  ...user,
107457
107616
  settingsReady: true,
@@ -107736,7 +107895,7 @@ function getSubtleCrypto() {
107736
107895
  async function importAesKey(key, usages) {
107737
107896
  return getSubtleCrypto().importKey("raw", toBytes32(key, "file key"), { name: "AES-GCM" }, false, usages);
107738
107897
  }
107739
- async function sealFile(_pair, key, bytes, scope) {
107898
+ async function sealFile(key, bytes, scope) {
107740
107899
  const fileKey = new Uint8Array(toBytes32(key, "file key"));
107741
107900
  try {
107742
107901
  const cryptoKey = await importAesKey(fileKey, ["encrypt"]);
@@ -107899,20 +108058,22 @@ async function toUploadBytes(data) {
107899
108058
  }
107900
108059
  return toBytes2(data, "upload bytes");
107901
108060
  }
107902
- async function makeChatFileUploadPayload(pair, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
107903
- const chatId = cleanMediaChatId(pair?.chatId);
108061
+ async function makeChatFileUploadPayload(chatId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
108062
+ const nextChatId = cleanMediaChatId(chatId);
107904
108063
  const mediaId = makeChatMediaId();
107905
- const path = mediaFilePath(chatId, mediaId);
108064
+ const path = mediaFilePath(nextChatId, mediaId);
107906
108065
  const expiresAt = Date.now() + CHAT_MEDIA_TTL_MS2;
107907
108066
  const key = createFileKey();
107908
108067
  try {
107909
108068
  const uploadBytes = await toUploadBytes(data);
107910
108069
  assertChatUploadByteSize(uploadBytes);
108070
+ const body = await sealFile(key, uploadBytes, path);
108071
+ assertChatUploadByteSize(body);
107911
108072
  return {
107912
- chatId,
108073
+ chatId: nextChatId,
107913
108074
  mediaId,
107914
108075
  path,
107915
- body: await sealFile(pair, key, uploadBytes, path),
108076
+ body,
107916
108077
  metadata: {
107917
108078
  contentType: "application/octet-stream",
107918
108079
  cacheControl
@@ -107941,10 +108102,12 @@ async function makeSharedFileUploadPayload(data, { contentType = "application/oc
107941
108102
  try {
107942
108103
  const uploadBytes = await toUploadBytes(data);
107943
108104
  assertChatUploadByteSize(uploadBytes);
108105
+ const body = await sealFile(key, uploadBytes, path);
108106
+ assertChatUploadByteSize(body);
107944
108107
  return {
107945
108108
  sharedId,
107946
108109
  path,
107947
- body: await sealFile(null, key, uploadBytes, path),
108110
+ body,
107948
108111
  metadata: {
107949
108112
  contentType,
107950
108113
  cacheControl
@@ -109870,10 +110033,19 @@ function sortedUniqueValues(items) {
109870
110033
  }
109871
110034
 
109872
110035
  // ../../shared/chat/chats.js
110036
+ function sameSigningKeys(left, right) {
110037
+ if (left === right)
110038
+ return true;
110039
+ const leftKeys = Object.keys(left || {});
110040
+ const rightKeys = Object.keys(right || {});
110041
+ if (leftKeys.length !== rightKeys.length)
110042
+ return false;
110043
+ return leftKeys.every((key) => left[key] === right[key]);
110044
+ }
109873
110045
  function sameChatShape(a, b) {
109874
110046
  if (!a || !b)
109875
110047
  return a === b;
109876
- 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);
110048
+ 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);
109877
110049
  }
109878
110050
  function sameChats(prev, next) {
109879
110051
  if (prev.length !== next.length)
@@ -111436,8 +111608,14 @@ function mediaAad(uid, network, id) {
111436
111608
  function partAad(uid, network, part) {
111437
111609
  return encoder.encode(JSON.stringify(["veyl-local-cache-part", CACHE_PART_ENVELOPE_VERSION, String(uid || ""), String(network || ""), String(part || "")]));
111438
111610
  }
111611
+ function encodePayloadData(payload) {
111612
+ return encoder.encode(JSON.stringify({ ...normalizePayload(payload), savedAt: Date.now() }));
111613
+ }
111614
+ function decodePayloadData(raw) {
111615
+ return JSON.parse(decoder.decode(toBytes2(raw, "cache payload")));
111616
+ }
111439
111617
  async function sealPayload(key, payload, uid, network) {
111440
- const body = encoder.encode(JSON.stringify({ ...normalizePayload(payload), savedAt: Date.now() }));
111618
+ const body = encodePayloadData(payload);
111441
111619
  try {
111442
111620
  const { iv, ct } = await sealAes(key, body, cacheAad(uid, network));
111443
111621
  return JSON.stringify({
@@ -111459,7 +111637,7 @@ async function openPayloadData(key, raw, uid, network) {
111459
111637
  }
111460
111638
  const plain = await openAes(key, fromHexBytes(envelope.iv, "cache iv"), fromHexBytes(envelope.ct, "cache ciphertext"), cacheAad(uid, network));
111461
111639
  try {
111462
- return JSON.parse(decoder.decode(plain));
111640
+ return decodePayloadData(plain);
111463
111641
  } finally {
111464
111642
  cleanBytes(plain);
111465
111643
  }
@@ -111520,6 +111698,14 @@ function mediaCrypto(uid, network, id, keyBytes) {
111520
111698
  ivBytes: AES_IV_BYTES
111521
111699
  };
111522
111700
  }
111701
+ function payloadCrypto(uid, network, keyBytes) {
111702
+ return {
111703
+ key: keyBytes,
111704
+ aad: cacheAad(uid, network),
111705
+ version: LOCAL_DATA_CACHE_VERSION,
111706
+ ivBytes: AES_IV_BYTES
111707
+ };
111708
+ }
111523
111709
  var MEDIA_ENVELOPE_VERSION = 1, CACHE_PART_ENVELOPE_VERSION = 1;
111524
111710
  var init_crypto = __esm(() => {
111525
111711
  init_aes2();
@@ -111555,11 +111741,32 @@ async function openVaultCache({ key, storage, uid, network }) {
111555
111741
  let writeTimer = null;
111556
111742
  let writeTask = Promise.resolve();
111557
111743
  const partWriteTasks = new Map;
111744
+ let storedPayload = null;
111558
111745
  try {
111559
- const rawPayload = await openPayloadData(cacheKey, await storage.read(), uid, network);
111560
- payload = normalizePayload(rawPayload);
111746
+ storedPayload = await storage.read();
111747
+ if (!storedPayload) {
111748
+ payload = emptyPayload();
111749
+ } else if (typeof storage.openPayload === "function") {
111750
+ const readKey = new Uint8Array(cacheKey);
111751
+ let plain;
111752
+ try {
111753
+ plain = await storage.openPayload(storedPayload, payloadCrypto(uid, network, readKey));
111754
+ payload = normalizePayload(decodePayloadData(plain));
111755
+ } finally {
111756
+ cleanBytes(plain, readKey);
111757
+ }
111758
+ } else {
111759
+ payload = normalizePayload(await openPayloadData(cacheKey, storedPayload, uid, network));
111760
+ }
111561
111761
  } catch {
111562
111762
  payload = emptyPayload();
111763
+ if (storedPayload) {
111764
+ await Promise.all([
111765
+ storage.remove().catch(() => {}),
111766
+ storage.removeAllMedia().catch(() => {}),
111767
+ storage.removeAllParts().catch(() => {})
111768
+ ]);
111769
+ }
111563
111770
  }
111564
111771
  const writeNow = () => {
111565
111772
  if (closed) {
@@ -111577,10 +111784,12 @@ async function openVaultCache({ key, storage, uid, network }) {
111577
111784
  return;
111578
111785
  }
111579
111786
  const writeKey = new Uint8Array(cacheKey);
111787
+ let body;
111580
111788
  try {
111581
- await storage.write(await sealPayload(writeKey, payload, uid, network));
111789
+ const raw = typeof storage.sealPayload === "function" ? await storage.sealPayload(body = encodePayloadData(payload), payloadCrypto(uid, network, writeKey)) : await sealPayload(writeKey, payload, uid, network);
111790
+ await storage.write(raw);
111582
111791
  } finally {
111583
- cleanBytes(writeKey);
111792
+ cleanBytes(body, writeKey);
111584
111793
  }
111585
111794
  }).catch(() => {});
111586
111795
  return writeTask;
@@ -112383,9 +112592,9 @@ function setErrorStage(error, stage, extra = {}) {
112383
112592
  Object.assign(error, extra);
112384
112593
  return error;
112385
112594
  }
112386
- async function makeChatFileUpload(pair, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
112595
+ async function makeChatFileUpload(chatId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
112387
112596
  try {
112388
- return await makeChatFileUploadPayload(pair, cid, data, {
112597
+ return await makeChatFileUploadPayload(chatId, cid, data, {
112389
112598
  cacheControl
112390
112599
  });
112391
112600
  } catch (error) {
@@ -112395,8 +112604,8 @@ async function makeChatFileUpload(pair, cid, data, { cacheControl = "private, ma
112395
112604
  });
112396
112605
  }
112397
112606
  }
112398
- async function putChatFile(pair, cid, data, options) {
112399
- const upload = await makeChatFileUpload(pair, cid, data, options);
112607
+ async function putChatFile(chatId, cid, data, options) {
112608
+ const upload = await makeChatFileUpload(chatId, cid, data, options);
112400
112609
  try {
112401
112610
  if (typeof options?.uploadChatMedia !== "function") {
112402
112611
  throw new Error("chat media upload required");
@@ -112431,7 +112640,7 @@ async function putSharedFile(data, options = {}) {
112431
112640
  throw setErrorStage(error, "upload", { path: upload.path });
112432
112641
  }
112433
112642
  }
112434
- async function readChatFile(readChatMedia, _pair, file) {
112643
+ async function readChatFile(readChatMedia, file) {
112435
112644
  let body;
112436
112645
  try {
112437
112646
  if (typeof readChatMedia !== "function") {
@@ -112489,8 +112698,8 @@ function buildAttachmentMsg(type, file) {
112489
112698
  return makeAttachment(type, file);
112490
112699
  }
112491
112700
  }
112492
- async function putAttachment(pair, cid, type, data, meta = {}) {
112493
- const file = await putChatFile(pair, cid, data, meta);
112701
+ async function putAttachment(chatId, cid, type, data, meta = {}) {
112702
+ const file = await putChatFile(chatId, cid, data, meta);
112494
112703
  return buildAttachmentMsg(type, {
112495
112704
  ...file,
112496
112705
  ...pickAttachmentMeta(meta)
@@ -112503,26 +112712,26 @@ async function putSharedAttachment(type, data, meta = {}) {
112503
112712
  ...pickAttachmentMeta(meta)
112504
112713
  });
112505
112714
  }
112506
- async function putImg(pair, cid, data, meta = {}) {
112507
- return putAttachment(pair, cid, "img", data, meta);
112715
+ async function putImg(chatId, cid, data, meta = {}) {
112716
+ return putAttachment(chatId, cid, "img", data, meta);
112508
112717
  }
112509
- async function putGif(pair, cid, data, meta = {}) {
112510
- return putAttachment(pair, cid, "gif", data, meta);
112718
+ async function putGif(chatId, cid, data, meta = {}) {
112719
+ return putAttachment(chatId, cid, "gif", data, meta);
112511
112720
  }
112512
- async function putM4a(pair, cid, data, meta = {}) {
112513
- return putAttachment(pair, cid, "m4a", data, meta);
112721
+ async function putM4a(chatId, cid, data, meta = {}) {
112722
+ return putAttachment(chatId, cid, "m4a", data, meta);
112514
112723
  }
112515
- async function putMp4(pair, cid, data, meta = {}) {
112516
- return putAttachment(pair, cid, "mp4", data, meta);
112724
+ async function putMp4(chatId, cid, data, meta = {}) {
112725
+ return putAttachment(chatId, cid, "mp4", data, meta);
112517
112726
  }
112518
- async function putFile(pair, cid, data, meta = {}) {
112519
- return putAttachment(pair, cid, "file", data, meta);
112727
+ async function putFile(chatId, cid, data, meta = {}) {
112728
+ return putAttachment(chatId, cid, "file", data, meta);
112520
112729
  }
112521
- function readMsgAttachment(readChatMedia, pair, msg) {
112522
- return readChatFile(readChatMedia, pair, msg);
112730
+ function readMsgAttachment(readChatMedia, msg) {
112731
+ return readChatFile(readChatMedia, msg);
112523
112732
  }
112524
- function readMsgFile(readChatMedia, pair, msg) {
112525
- return readMsgAttachment(readChatMedia, pair, msg);
112733
+ function readMsgFile(readChatMedia, msg) {
112734
+ return readMsgAttachment(readChatMedia, msg);
112526
112735
  }
112527
112736
  var init_media2 = __esm(() => {
112528
112737
  init_files2();
@@ -112771,14 +112980,6 @@ function ownerEditedPreview(senderPubkey, message) {
112771
112980
  failed: false
112772
112981
  };
112773
112982
  }
112774
- function messageFileChatId(message) {
112775
- try {
112776
- const ref = getMediaFileRef(message?.p);
112777
- return ref?.type === "chat" ? ref.chatId : "";
112778
- } catch {
112779
- return "";
112780
- }
112781
- }
112782
112983
  async function recipientForSend(cloud, receiverChatPK, options, needed) {
112783
112984
  if (!needed) {
112784
112985
  return null;
@@ -112799,50 +113000,51 @@ async function sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, messa
112799
113000
  if (!chatId) {
112800
113001
  throw new Error("chat id required");
112801
113002
  }
112802
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112803
- if (options?.linkId && cleanText(options.linkId) !== pair.linkId) {
112804
- throw new Error("link mismatch");
112805
- }
112806
- const retention = cleanChatRetention(options?.retention ?? options?.ttlMode);
112807
- const tsMs = Date.now();
112808
- const messagePayload = withMessageRetention(message, retention);
112809
- const actionOp = actionOpForPayload(messagePayload);
112810
- const { head, body } = await sealMsg(pair, messagePayload, { op: actionOp, ts: tsMs });
112811
- const ttlMs = actionOp === CHAT_ACTION_OPS.CREATE ? newStorageTtlMs() : null;
112812
- const messageId = head.cid;
112813
- const msgData = {
112814
- head,
112815
- body,
112816
- ttlMs
112817
- };
112818
- const recipientProfile = await recipientForSend(cloud, receiverChatPK, options, updatePreview || options?.ping === true);
112819
- const sentMessage = ownerPreview(senderPubkey, messagePayload, messageId, head, tsMs, ttlMs);
112820
- const preview2 = updatePreview ? sentMessage : null;
112821
- const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112822
- peerUid: recipientProfile?.uid || cleanText(options?.receiverUid),
112823
- peerSigningPublicKey: recipientProfile?.signingPublicKey,
112824
- entry: options?.ownEntry,
112825
- settings: options?.chatSettings,
112826
- preview: preview2,
112827
- ts: tsMs
112828
- }) : null;
112829
- const pingKind = updatePreview ? "message" : cleanText(options?.pingKind) || "ping";
112830
- const ping = recipientProfile?.uid && (updatePreview || options?.ping === true) ? await sealPing(senderPubkey, senderPrivkey, receiverChatPK, {
112831
- kind: pingKind,
112832
- chatId,
112833
- senderUid: cleanText(options?.senderUid),
112834
- messageId,
112835
- ts: tsMs
112836
- }) : null;
112837
- const write = await cloud.chat.messages.send({
112838
- chatId,
112839
- messageId,
112840
- message: msgData,
112841
- ownerEntry,
112842
- inbox: ping ? { recipientUid: recipientProfile?.uid, ping } : null,
112843
- onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
113003
+ return withCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId }, async (pair) => {
113004
+ if (options?.linkId && cleanText(options.linkId) !== pair.linkId) {
113005
+ throw new Error("link mismatch");
113006
+ }
113007
+ const retention = cleanChatRetention(options?.retention ?? options?.ttlMode);
113008
+ const tsMs = Date.now();
113009
+ const messagePayload = withMessageRetention(message, retention);
113010
+ const actionOp = actionOpForPayload(messagePayload);
113011
+ const { head, body } = await sealMsg(pair, messagePayload, { op: actionOp, ts: tsMs });
113012
+ const ttlMs = actionOp === CHAT_ACTION_OPS.CREATE ? newStorageTtlMs() : null;
113013
+ const messageId = head.cid;
113014
+ const msgData = {
113015
+ head,
113016
+ body,
113017
+ ttlMs
113018
+ };
113019
+ const recipientProfile = await recipientForSend(cloud, receiverChatPK, options, updatePreview || options?.ping === true);
113020
+ const sentMessage = ownerPreview(senderPubkey, messagePayload, messageId, head, tsMs, ttlMs);
113021
+ const preview2 = updatePreview ? sentMessage : null;
113022
+ const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
113023
+ peerUid: recipientProfile?.uid || cleanText(options?.receiverUid),
113024
+ peerSigningPublicKey: recipientProfile?.signingPublicKey,
113025
+ entry: options?.ownEntry,
113026
+ settings: options?.chatSettings,
113027
+ preview: preview2,
113028
+ ts: tsMs
113029
+ }) : null;
113030
+ const pingKind = updatePreview ? "message" : cleanText(options?.pingKind) || "ping";
113031
+ const ping = recipientProfile?.uid && (updatePreview || options?.ping === true) ? await sealPing(senderPubkey, senderPrivkey, receiverChatPK, {
113032
+ kind: pingKind,
113033
+ chatId,
113034
+ senderUid: cleanText(options?.senderUid),
113035
+ messageId,
113036
+ ts: tsMs
113037
+ }) : null;
113038
+ const write = await cloud.chat.messages.send({
113039
+ chatId,
113040
+ messageId,
113041
+ message: msgData,
113042
+ ownerEntry,
113043
+ inbox: ping ? { recipientUid: recipientProfile?.uid, ping } : null,
113044
+ onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
113045
+ });
113046
+ return { chatId, msgId: messageId, cid: head.cid, message: sentMessage, preview: preview2, ...write?.skipped ? { skipped: true } : {} };
112844
113047
  });
112845
- return { chatId, msgId: messageId, cid: head.cid, message: sentMessage, preview: preview2, ...write?.skipped ? { skipped: true } : {} };
112846
113048
  }
112847
113049
  async function sendReadReceipt(cloud, senderPubkey, senderPrivkey, receiverChatPK, target, options = {}) {
112848
113050
  const receipt = {
@@ -112952,27 +113154,28 @@ async function setChatRetention(cloud, chatId, senderPubkey, senderPrivkey, peer
112952
113154
  throw new Error("vault locked");
112953
113155
  }
112954
113156
  const nextRetention = cleanChatRetention(retention);
112955
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
112956
- if (chatId && pair.chatId !== chatId) {
112957
- throw new Error("chat mismatch");
112958
- }
112959
- const systemMessage = {
112960
- ...makeRetentionSystemMsg(nextRetention),
112961
- cid: cleanText(options?.cid) || makeCid(),
112962
- s: senderPubkey
112963
- };
112964
- await sendMsg(cloud, senderPubkey, senderPrivkey, peerChatPK, systemMessage, {
112965
- chatId,
112966
- linkId: pair.linkId,
112967
- updatePreview: true,
112968
- retention: nextRetention,
112969
- chatExists: true,
112970
- senderUid: options?.senderUid,
112971
- ownEntry: options?.ownEntry,
112972
- chatSettings: { retention: nextRetention },
112973
- onCommitted: options?.onCommitted
113157
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
113158
+ if (chatId && pair.chatId !== chatId) {
113159
+ throw new Error("chat mismatch");
113160
+ }
113161
+ const systemMessage = {
113162
+ ...makeRetentionSystemMsg(nextRetention),
113163
+ cid: cleanText(options?.cid) || makeCid(),
113164
+ s: senderPubkey
113165
+ };
113166
+ await sendMsg(cloud, senderPubkey, senderPrivkey, peerChatPK, systemMessage, {
113167
+ chatId,
113168
+ linkId: pair.linkId,
113169
+ updatePreview: true,
113170
+ retention: nextRetention,
113171
+ chatExists: true,
113172
+ senderUid: options?.senderUid,
113173
+ ownEntry: options?.ownEntry,
113174
+ chatSettings: { retention: nextRetention },
113175
+ onCommitted: options?.onCommitted
113176
+ });
113177
+ return nextRetention;
112974
113178
  });
112975
- return nextRetention;
112976
113179
  }
112977
113180
  async function uploadAttachmentMsg(_cloud, senderPubkey, senderPrivkey, receiverChatPK, attachment = {}) {
112978
113181
  if (!senderPrivkey || !senderPubkey) {
@@ -112986,95 +113189,96 @@ async function uploadAttachmentMsg(_cloud, senderPubkey, senderPrivkey, receiver
112986
113189
  if (!chatId) {
112987
113190
  throw new Error("chat id required");
112988
113191
  }
112989
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112990
113192
  const type = cleanText(attachment?.type) || "file";
112991
113193
  const data = attachment?.data;
112992
113194
  const meta = attachment?.meta || {};
112993
113195
  switch (type) {
112994
113196
  case "img":
112995
- return putImg(pair, nextCid, data, meta);
113197
+ return putImg(chatId, nextCid, data, meta);
112996
113198
  case "gif":
112997
- return putGif(pair, nextCid, data, meta);
113199
+ return putGif(chatId, nextCid, data, meta);
112998
113200
  case "m4a":
112999
- return putM4a(pair, nextCid, data, meta);
113201
+ return putM4a(chatId, nextCid, data, meta);
113000
113202
  case "mp4":
113001
- return putMp4(pair, nextCid, data, meta);
113203
+ return putMp4(chatId, nextCid, data, meta);
113002
113204
  case "file":
113003
- return putFile(pair, nextCid, data, meta);
113205
+ return putFile(chatId, nextCid, data, meta);
113004
113206
  default:
113005
- return putAttachment(pair, nextCid, type, data, meta);
113207
+ return putAttachment(chatId, nextCid, type, data, meta);
113006
113208
  }
113007
113209
  }
113008
113210
  async function updateMsg(cloud, chatId, msgId, senderPubkey, senderPrivkey, receiverChatPK, newMessage, options = {}) {
113009
113211
  if (!senderPubkey || !senderPrivkey)
113010
113212
  throw new Error("vault locked");
113011
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
113012
- if (pair.chatId !== chatId) {
113013
- throw new Error("chat mismatch");
113014
- }
113015
- const target = cleanText(newMessage?.cid) || cleanText(msgId);
113016
- if (!target) {
113017
- throw new Error("message target required");
113018
- }
113019
- const op = newMessage?.t === "req" && cleanText(newMessage?.tx) ? CHAT_ACTION_OPS.PAY_CONFIRM : CHAT_ACTION_OPS.EDIT;
113020
- const tsMs = Date.now();
113021
- const action = {
113022
- ...newMessage || {},
113023
- cid: makeCid(),
113024
- s: senderPubkey
113025
- };
113026
- const { head, body } = await sealMsg(pair, action, { op, target, ts: tsMs });
113027
- const messageId = head.cid;
113028
- const updatePreview = options?.updatePreview === true;
113029
- const preview2 = updatePreview ? ownerEditedPreview(senderPubkey, newMessage) : null;
113030
- const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
113031
- peerUid: cleanText(options?.receiverUid),
113032
- peerSigningPublicKey: cleanText(options?.peerSigningPublicKey),
113033
- entry: options?.ownEntry,
113034
- preview: preview2,
113035
- ts: preview2?.ts ?? tsMs
113036
- }) : null;
113037
- await cloud.chat.messages.send({
113038
- chatId,
113039
- messageId,
113040
- message: {
113041
- head,
113042
- body,
113043
- ttlMs: null
113044
- },
113045
- ownerEntry,
113046
- inbox: null
113213
+ return withCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId }, async (pair) => {
113214
+ if (pair.chatId !== chatId) {
113215
+ throw new Error("chat mismatch");
113216
+ }
113217
+ const target = cleanText(newMessage?.cid) || cleanText(msgId);
113218
+ if (!target) {
113219
+ throw new Error("message target required");
113220
+ }
113221
+ const op = newMessage?.t === "req" && cleanText(newMessage?.tx) ? CHAT_ACTION_OPS.PAY_CONFIRM : CHAT_ACTION_OPS.EDIT;
113222
+ const tsMs = Date.now();
113223
+ const action = {
113224
+ ...newMessage || {},
113225
+ cid: makeCid(),
113226
+ s: senderPubkey
113227
+ };
113228
+ const { head, body } = await sealMsg(pair, action, { op, target, ts: tsMs });
113229
+ const messageId = head.cid;
113230
+ const updatePreview = options?.updatePreview === true;
113231
+ const preview2 = updatePreview ? ownerEditedPreview(senderPubkey, newMessage) : null;
113232
+ const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
113233
+ peerUid: cleanText(options?.receiverUid),
113234
+ peerSigningPublicKey: cleanText(options?.peerSigningPublicKey),
113235
+ entry: options?.ownEntry,
113236
+ preview: preview2,
113237
+ ts: preview2?.ts ?? tsMs
113238
+ }) : null;
113239
+ await cloud.chat.messages.send({
113240
+ chatId,
113241
+ messageId,
113242
+ message: {
113243
+ head,
113244
+ body,
113245
+ ttlMs: null
113246
+ },
113247
+ ownerEntry,
113248
+ inbox: null
113249
+ });
113250
+ return {
113251
+ chatId,
113252
+ msgId: messageId,
113253
+ cid: head.cid,
113254
+ preview: preview2
113255
+ };
113047
113256
  });
113048
- return {
113049
- chatId,
113050
- msgId: messageId,
113051
- cid: head.cid,
113052
- preview: preview2
113053
- };
113054
113257
  }
113055
113258
  async function deleteMsg(cloud, chatId, messageOrId, senderPubkey, senderPrivkey, peerChatPK, options = {}) {
113056
113259
  if (!cloud || !chatId || !messageOrId || !senderPubkey || !senderPrivkey || !peerChatPK) {
113057
113260
  return false;
113058
113261
  }
113059
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
113060
- if (pair.chatId !== chatId) {
113061
- return false;
113062
- }
113063
- const item = messageDeleteItems([messageOrId])[0];
113064
- const target = cleanText(options?.docId) || cleanText(item?.id);
113065
- if (!target || target.startsWith("local:")) {
113066
- return false;
113067
- }
113068
- const deleteTarget = cleanText(options?.target) || cleanText(getMessageKey(messageOrId)) || target;
113069
- await sendDeleteAction(cloud, senderPubkey, senderPrivkey, peerChatPK, deleteTarget, {
113070
- chatId,
113071
- linkId: options?.linkId,
113072
- senderUid: options?.senderUid
113073
- });
113074
- await cloud.chat.messages.delete(chatId, target, {
113075
- mediaPaths: item?.mediaPath ? [item.mediaPath] : []
113262
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
113263
+ if (pair.chatId !== chatId) {
113264
+ return false;
113265
+ }
113266
+ const item = messageDeleteItems([messageOrId])[0];
113267
+ const target = cleanText(options?.docId) || cleanText(item?.id);
113268
+ if (!target || target.startsWith("local:")) {
113269
+ return false;
113270
+ }
113271
+ const deleteTarget = cleanText(options?.target) || cleanText(getMessageKey(messageOrId)) || target;
113272
+ await sendDeleteAction(cloud, senderPubkey, senderPrivkey, peerChatPK, deleteTarget, {
113273
+ chatId,
113274
+ linkId: options?.linkId,
113275
+ senderUid: options?.senderUid
113276
+ });
113277
+ await cloud.chat.messages.delete(chatId, target, {
113278
+ mediaPaths: item?.mediaPath ? [item.mediaPath] : []
113279
+ });
113280
+ return true;
113076
113281
  });
113077
- return true;
113078
113282
  }
113079
113283
  async function deleteMsgs(cloud, chatId, messages, senderPubkey, senderPrivkey, peerChatPK) {
113080
113284
  if (!cloud || !chatId || !senderPubkey || !senderPrivkey || !peerChatPK) {
@@ -113084,31 +113288,31 @@ async function deleteMsgs(cloud, chatId, messages, senderPubkey, senderPrivkey,
113084
113288
  if (!items.length) {
113085
113289
  return 0;
113086
113290
  }
113087
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
113088
- if (pair.chatId !== chatId) {
113089
- return 0;
113090
- }
113091
- let deleted = 0;
113092
- for (let index = 0;index < items.length; index += DELETE_WRITE_BATCH_SIZE) {
113093
- const chunk = items.slice(index, index + DELETE_WRITE_BATCH_SIZE);
113094
- const targets = chunk.map((item) => cleanText(item.id)).filter((target) => target && !target.startsWith("local:"));
113095
- const mediaPaths = chunk.map((item) => cleanText(item.mediaPath)).filter(Boolean);
113096
- deleted += await cloud.chat.messages.deleteMany(chatId, targets, { mediaPaths });
113097
- }
113098
- return deleted;
113291
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
113292
+ if (pair.chatId !== chatId) {
113293
+ return 0;
113294
+ }
113295
+ let deleted = 0;
113296
+ for (let index = 0;index < items.length; index += DELETE_WRITE_BATCH_SIZE) {
113297
+ const chunk = items.slice(index, index + DELETE_WRITE_BATCH_SIZE);
113298
+ const targets = chunk.map((item) => cleanText(item.id)).filter((target) => target && !target.startsWith("local:"));
113299
+ const mediaPaths = chunk.map((item) => cleanText(item.mediaPath)).filter(Boolean);
113300
+ deleted += await cloud.chat.messages.deleteMany(chatId, targets, { mediaPaths });
113301
+ }
113302
+ return deleted;
113303
+ });
113099
113304
  }
113100
- async function readMsgMedia(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg) {
113101
- return readMsgAttachment2(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg);
113305
+ async function readMsgMedia(readChatMedia, msg) {
113306
+ return readMsgAttachment2(readChatMedia, msg);
113102
113307
  }
113103
- async function readMsgAttachment2(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg) {
113308
+ async function readMsgAttachment2(readChatMedia, msg) {
113104
113309
  if (typeof readChatMedia !== "function") {
113105
113310
  throw new Error("chat media read required");
113106
113311
  }
113107
- if (!userChatPK || !userPrivKey || !peerChatPK || !msg) {
113312
+ if (!msg) {
113108
113313
  return null;
113109
113314
  }
113110
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: msg?.chatId || messageFileChatId(msg) });
113111
- return readMsgFile(readChatMedia, pair, msg);
113315
+ return readMsgFile(readChatMedia, msg);
113112
113316
  }
113113
113317
  var DELETE_WRITE_BATCH_SIZE;
113114
113318
  var init_write = __esm(() => {
@@ -113351,14 +113555,14 @@ function hasInvalidStoredMediaRef(message) {
113351
113555
  return true;
113352
113556
  }
113353
113557
  }
113354
- function readMessageMedia(cloud, media, chatPK, chatPrivateKey, peerChatPK, message) {
113558
+ function readMessageMedia(cloud, media, message) {
113355
113559
  const readChatMedia = cloud?.chat?.media?.read;
113356
113560
  if (typeof media?.readMessageFile === "function") {
113357
- return media.readMessageFile(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
113561
+ return media.readMessageFile(readChatMedia, message);
113358
113562
  }
113359
- return readMsgMedia(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
113563
+ return readMsgMedia(readChatMedia, message);
113360
113564
  }
113361
- function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey, localCache }) {
113565
+ function createChatSave({ cloud, media = {}, chatBanned, localCache }) {
113362
113566
  const makeMessagePermanent = async (chatId, message) => {
113363
113567
  if (chatBanned) {
113364
113568
  throw makeChatUnavailableError();
@@ -113376,7 +113580,7 @@ function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey,
113376
113580
  const updated = await makeMsgTemporary(cloud, chatId, list, ttl);
113377
113581
  return { updated, ttl };
113378
113582
  };
113379
- const readMessageFile = async (peerChatPK, message) => {
113583
+ const readMessageFile = async (_peerChatPK, message) => {
113380
113584
  if (chatBanned) {
113381
113585
  throw makeChatUnavailableError();
113382
113586
  }
@@ -113398,7 +113602,7 @@ function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey,
113398
113602
  throw makeFileGoneError();
113399
113603
  }
113400
113604
  try {
113401
- const bytes = await readMessageMedia(cloud, media, chatPK, chatPrivateKey, peerChatPK, message);
113605
+ const bytes = await readMessageMedia(cloud, media, message);
113402
113606
  saveMedia(localCache, message, bytes, message);
113403
113607
  return bytes;
113404
113608
  } catch (error) {
@@ -114044,8 +114248,11 @@ function withSharedMediaUpload(cloud, media, meta = {}) {
114044
114248
  };
114045
114249
  }
114046
114250
  function uploadMessageAttachment(cloud, media, senderPubkey, senderPrivkey, receiverChatPK, attachment = {}) {
114251
+ if (!senderPubkey || !senderPrivkey) {
114252
+ throw new Error("vault locked");
114253
+ }
114047
114254
  if (typeof media?.uploadAttachment === "function") {
114048
- return media.uploadAttachment(senderPubkey, senderPrivkey, receiverChatPK, attachment);
114255
+ return media.uploadAttachment(attachment);
114049
114256
  }
114050
114257
  return uploadAttachmentMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, withMediaUpload(cloud, media, attachment));
114051
114258
  }
@@ -114343,17 +114550,21 @@ ${cid}` : "";
114343
114550
  const requiredPreview = previewRequired || sendOptions.chatExists !== true;
114344
114551
  let local = await showLocalMessage(peerChatPK, message, sendOptions);
114345
114552
  let committed = false;
114553
+ let sent = false;
114554
+ const markSent = () => {
114555
+ if (sent)
114556
+ return;
114557
+ sent = true;
114558
+ sentChatIdsRef.current.add(local.chatId);
114559
+ markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
114560
+ };
114346
114561
  const markCommitted = () => {
114347
114562
  if (committed)
114348
114563
  return;
114349
114564
  committed = true;
114350
- sentChatIdsRef.current.add(local.chatId);
114565
+ markSent();
114351
114566
  markDone(diag, "chat.send.commit", startedAt, { type: local?.t || "txt", previewRequired: !!requiredPreview });
114352
114567
  };
114353
- const markSent = () => {
114354
- sentChatIdsRef.current.add(local.chatId);
114355
- markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
114356
- };
114357
114568
  return await new Promise((resolve2, reject) => {
114358
114569
  const job = {
114359
114570
  previewKey: local.chatId,
@@ -114571,7 +114782,8 @@ ${cid}` : "";
114571
114782
  linkId: local.linkId,
114572
114783
  version: local.version,
114573
114784
  sendOptions,
114574
- nextAttachment: prepared.nextAttachment
114785
+ nextAttachment: prepared.nextAttachment,
114786
+ committed: false
114575
114787
  };
114576
114788
  }));
114577
114789
  return await new Promise((resolve2, reject) => {
@@ -114580,7 +114792,9 @@ ${cid}` : "";
114580
114792
  reject,
114581
114793
  onError: () => {
114582
114794
  for (const item of locals) {
114583
- markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114795
+ if (!item.committed) {
114796
+ markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114797
+ }
114584
114798
  }
114585
114799
  },
114586
114800
  run: async () => {
@@ -114595,6 +114809,7 @@ ${cid}` : "";
114595
114809
  results.push({ peerChatPK: item.peerChatPK, ok: false, error: uploadError });
114596
114810
  continue;
114597
114811
  }
114812
+ let sentMessage;
114598
114813
  try {
114599
114814
  let uploaded = uploads.get(uploadKey);
114600
114815
  if (!uploaded) {
@@ -114607,17 +114822,32 @@ ${cid}` : "";
114607
114822
  cid: item.cid,
114608
114823
  s: chatPK
114609
114824
  };
114610
- const sentMessage = withMessageRetention(sent, item.sendOptions.retention);
114825
+ sentMessage = withMessageRetention(sent, item.sendOptions.retention);
114611
114826
  rememberCachedLocalMedia(item.chatId, item.cid, sentMessage);
114612
- await sendMsg(cloud, chatPK, chatPrivateKey, item.peerChatPK, sentMessage, { ...item.sendOptions, chatId: item.chatId, linkId: item.linkId, senderUid: uid });
114613
- markLocalStatus(item.chatId, item.cid, LOCAL_SENT);
114827
+ await sendMsg(cloud, chatPK, chatPrivateKey, item.peerChatPK, sentMessage, {
114828
+ ...item.sendOptions,
114829
+ chatId: item.chatId,
114830
+ linkId: item.linkId,
114831
+ senderUid: uid,
114832
+ onCommitted: () => {
114833
+ if (item.committed)
114834
+ return;
114835
+ item.committed = true;
114836
+ sentChatIdsRef.current.add(item.chatId);
114837
+ markLocalStatus(item.chatId, item.cid, LOCAL_SENT);
114838
+ }
114839
+ });
114614
114840
  results.push({ peerChatPK: item.peerChatPK, ok: true, message: sentMessage });
114615
114841
  } catch (error) {
114616
114842
  if (!uploads.has(uploadKey)) {
114617
114843
  uploadErrors.set(uploadKey, error);
114618
114844
  }
114619
- markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114620
- results.push({ peerChatPK: item.peerChatPK, ok: false, error });
114845
+ if (item.committed) {
114846
+ results.push({ peerChatPK: item.peerChatPK, ok: true, message: sentMessage });
114847
+ } else {
114848
+ markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114849
+ results.push({ peerChatPK: item.peerChatPK, ok: false, error });
114850
+ }
114621
114851
  }
114622
114852
  }
114623
114853
  return results;
@@ -115424,7 +115654,6 @@ async function decryptRecordEntries(records, userChatPK, userPrivKey, peerChatPK
115424
115654
  return [];
115425
115655
  }
115426
115656
  const signingKeysVersion = signingKeysFingerprint(options?.signingKeysByChatKey);
115427
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId });
115428
115657
  const entryOptions = { ...options, signingKeysVersion };
115429
115658
  const shouldYield = positiveInt(options?.yieldEvery, 0) > 0;
115430
115659
  const entries = new Array(source.length).fill(null);
@@ -115441,38 +115670,40 @@ async function decryptRecordEntries(records, userChatPK, userPrivKey, peerChatPK
115441
115670
  }
115442
115671
  pending.push({ record, index });
115443
115672
  };
115444
- const openPending = async (pending) => {
115445
- if (!pending.length) {
115446
- return;
115447
- }
115448
- const opened = await decryptRecordBatchWithPair(pending.map((item) => item.record), pair, cache, entryOptions);
115449
- for (let index = 0;index < opened.length; index += 1) {
115450
- const entry = opened[index];
115451
- if (entry) {
115452
- entries[pending[index].index] = entry;
115673
+ return withCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId }, async (pair) => {
115674
+ const openPending = async (pending) => {
115675
+ if (!pending.length) {
115676
+ return;
115677
+ }
115678
+ const opened = await decryptRecordBatchWithPair(pending.map((item) => item.record), pair, cache, entryOptions);
115679
+ for (let index = 0;index < opened.length; index += 1) {
115680
+ const entry = opened[index];
115681
+ if (entry) {
115682
+ entries[pending[index].index] = entry;
115683
+ }
115684
+ }
115685
+ };
115686
+ if (!options?.signal && !shouldYield) {
115687
+ const pending = [];
115688
+ for (let index = 0;index < source.length; index += 1) {
115689
+ collectRecord(source[index], index, pending);
115453
115690
  }
115691
+ await openPending(pending);
115692
+ return entries.filter(Boolean);
115454
115693
  }
115455
- };
115456
- if (!options?.signal && !shouldYield) {
115457
- const pending = [];
115458
- for (let index = 0;index < source.length; index += 1) {
115459
- collectRecord(source[index], index, pending);
115694
+ const chunkSize = positiveInt(options?.yieldEvery, source.length || 1);
115695
+ for (let index = 0;index < source.length; index += chunkSize) {
115696
+ throwIfAborted(options?.signal);
115697
+ const chunk = source.slice(index, index + chunkSize);
115698
+ const pending = [];
115699
+ for (let offset = 0;offset < chunk.length; offset += 1) {
115700
+ collectRecord(chunk[offset], index + offset, pending);
115701
+ }
115702
+ await openPending(pending);
115703
+ await maybeYield(index + chunk.length, options);
115460
115704
  }
115461
- await openPending(pending);
115462
115705
  return entries.filter(Boolean);
115463
- }
115464
- const chunkSize = positiveInt(options?.yieldEvery, source.length || 1);
115465
- for (let index = 0;index < source.length; index += chunkSize) {
115466
- throwIfAborted(options?.signal);
115467
- const chunk = source.slice(index, index + chunkSize);
115468
- const pending = [];
115469
- for (let offset = 0;offset < chunk.length; offset += 1) {
115470
- collectRecord(chunk[offset], index + offset, pending);
115471
- }
115472
- await openPending(pending);
115473
- await maybeYield(index + chunk.length, options);
115474
- }
115475
- return entries.filter(Boolean);
115706
+ });
115476
115707
  }
115477
115708
  function messageQueryLimit(pageSize) {
115478
115709
  return positiveInt(pageSize, MSG_BATCH_SIZE);
@@ -115818,9 +116049,10 @@ async function decryptMsg(msgData, userChatPK, userPrivKey, peerChatPK, options
115818
116049
  return null;
115819
116050
  }
115820
116051
  try {
115821
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId });
115822
- const message = await openMsg(pair, msgData, { signingKeysByChatKey: options?.signingKeysByChatKey, crypto: options?.crypto });
115823
- return normalizeDecryptedMsg(msgData, message);
116052
+ return await withCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId }, async (pair) => {
116053
+ const message = await openMsg(pair, msgData, { signingKeysByChatKey: options?.signingKeysByChatKey, crypto: options?.crypto });
116054
+ return normalizeDecryptedMsg(msgData, message);
116055
+ });
115824
116056
  } catch {
115825
116057
  return null;
115826
116058
  }
@@ -116890,12 +117122,12 @@ function warmKeyStore(ref) {
116890
117122
  }
116891
117123
  return ref.current;
116892
117124
  }
116893
- function readMessageMedia2(cloud, media, chatPK, chatPrivateKey, peerChatPK, message) {
117125
+ function readMessageMedia2(cloud, media, message) {
116894
117126
  const readChatMedia = cloud?.chat?.media?.read;
116895
117127
  if (typeof media?.readMessageFile === "function") {
116896
- return media.readMessageFile(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
117128
+ return media.readMessageFile(readChatMedia, message);
116897
117129
  }
116898
- return readMsgMedia(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
117130
+ return readMsgMedia(readChatMedia, message);
116899
117131
  }
116900
117132
  function isDenied3(error) {
116901
117133
  return error?.code === "permission-denied";
@@ -117102,12 +117334,12 @@ function createChatMessageBatches({ cloud, media = {}, chatPK, chatPrivateKey, c
117102
117334
  return;
117103
117335
  }
117104
117336
  const tasks = buildMediaTasks();
117105
- const readFile4 = async (peerChatPK, message) => {
117337
+ const readFile4 = async (_peerChatPK, message) => {
117106
117338
  const cached = await readCachedMedia(localCache, message);
117107
117339
  if (cached?.byteLength) {
117108
117340
  return cached;
117109
117341
  }
117110
- const bytes = await readMessageMedia2(cloud, media, chatPK, chatPrivateKey, peerChatPK, message);
117342
+ const bytes = await readMessageMedia2(cloud, media, message);
117111
117343
  if (bytes?.byteLength) {
117112
117344
  saveMedia(localCache, message, bytes, message);
117113
117345
  }
@@ -117854,6 +118086,16 @@ function pingSigningKeys(ping, existing) {
117854
118086
  [ping.payload.senderChatPK]: ping.payload.signingPublicKey
117855
118087
  };
117856
118088
  }
118089
+ function chatSettingsFromPreview(settings, preview2) {
118090
+ const current = normalizeChatSettings(settings);
118091
+ if (!isSystemMsg(preview2)) {
118092
+ return current;
118093
+ }
118094
+ return {
118095
+ ...current,
118096
+ retention: cleanChatRetention(preview2.retention)
118097
+ };
118098
+ }
117857
118099
  function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options = {}) {
117858
118100
  const ts = timestampMs(ms, null) ?? timestampMs(existing?.ts, null) ?? timestampMs(preview2?.ts, null) ?? 0;
117859
118101
  if (!ts) {
@@ -117870,7 +118112,7 @@ function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options
117870
118112
  peerChatPK: ping.payload.senderChatPK,
117871
118113
  peerUid: existing?.peerUid || cleanText(ping.payload.senderUid) || null,
117872
118114
  signingKeysByChatKey: pingSigningKeys(ping, existing),
117873
- settings: existing?.settings,
118115
+ settings: chatSettingsFromPreview(existing?.settings, preview2),
117874
118116
  saved: existing?.saved || null,
117875
118117
  readMs,
117876
118118
  startMs,
@@ -117929,7 +118171,7 @@ async function savePing(cloud, uid, userChatPK, userPrivKey, ping, options = {})
117929
118171
  peerUid: peerUid2 || existing?.peerUid,
117930
118172
  peerSigningPublicKey: ping.payload.signingPublicKey || existing?.signingKeysByChatKey?.[ping.payload.senderChatPK],
117931
118173
  signingKeysByChatKey,
117932
- settings: existing?.settings,
118174
+ settings: chatSettingsFromPreview(existing?.settings, preview2),
117933
118175
  preview: preview2 || existing?.preview,
117934
118176
  readMs: existing?.readMs,
117935
118177
  startMs: existing ? existing.startMs : timestampMs(options.startPreview?.ts ?? preview2?.ts, null, { positive: true })
@@ -118118,6 +118360,7 @@ var init_inbox = __esm(() => {
118118
118360
  init_ping();
118119
118361
  init_query();
118120
118362
  init_chats();
118363
+ init_ttl();
118121
118364
  init_time();
118122
118365
  init_protocol();
118123
118366
  });
@@ -118613,6 +118856,7 @@ function createChatList({
118613
118856
  let chats = [];
118614
118857
  let peers = [];
118615
118858
  let isChatDataReady = false;
118859
+ let isChatListLive = false;
118616
118860
  let chatSourceReady = false;
118617
118861
  let lastChat = null;
118618
118862
  let serverChatIds = [];
@@ -118649,6 +118893,7 @@ function createChatList({
118649
118893
  chats,
118650
118894
  peers,
118651
118895
  isChatDataReady,
118896
+ isChatListLive,
118652
118897
  hasChats: chats.length > 0,
118653
118898
  previewNow,
118654
118899
  lastChat,
@@ -118682,6 +118927,13 @@ function createChatList({
118682
118927
  isChatDataReady = value;
118683
118928
  publish();
118684
118929
  };
118930
+ const setIsChatListLive = (next) => {
118931
+ const value = nextValue(isChatListLive, next);
118932
+ if (Object.is(isChatListLive, value))
118933
+ return;
118934
+ isChatListLive = value;
118935
+ publish();
118936
+ };
118685
118937
  const setLastChat = (next) => {
118686
118938
  const value = nextValue(lastChat, next);
118687
118939
  if (Object.is(lastChat, value))
@@ -118891,6 +119143,7 @@ function createChatList({
118891
119143
  const resetChatList = (ready = false) => {
118892
119144
  chatSourceReady = ready;
118893
119145
  syncChatDataReady();
119146
+ setIsChatListLive(false);
118894
119147
  setChats((prev) => prev.length ? [] : prev);
118895
119148
  setPeers((prev) => prev.length ? [] : prev);
118896
119149
  setLastChat((prev) => prev ? null : prev);
@@ -118980,6 +119233,7 @@ function createChatList({
118980
119233
  resetAllChatState();
118981
119234
  return;
118982
119235
  }
119236
+ setIsChatListLive(false);
118983
119237
  let cancelled = false;
118984
119238
  const hydrateKey = localCache?.id ? `${localCache.id}:${chatPK}` : "";
118985
119239
  if (hydrateKey && lastHydratedCacheKeyRef.current !== hydrateKey) {
@@ -119079,6 +119333,7 @@ function createChatList({
119079
119333
  writeCache: true,
119080
119334
  serverEndReached: cachedHistoryEndRef.current
119081
119335
  });
119336
+ setIsChatListLive(true);
119082
119337
  listenUpdateSeqRef.current += 1;
119083
119338
  markDiag(diag, "chat.provider.listen.update", {
119084
119339
  seq: listenUpdateSeqRef.current,
@@ -119121,6 +119376,7 @@ function createChatList({
119121
119376
  markChatSourceReady();
119122
119377
  fillVisibleChatPage();
119123
119378
  }, (error) => {
119379
+ setIsChatListLive(false);
119124
119380
  markError(diag, "chat.provider.listen", listenStartedAt, error);
119125
119381
  if (shouldResetChatListAfterListenError(error)) {
119126
119382
  resetAllChatState();
@@ -122810,11 +123066,6 @@ function createWalletPrivacy({ wallet, ghostWallet, diag }) {
122810
123066
  const startedAt = Date.now();
122811
123067
  const desired = desiredPrivacy;
122812
123068
  markDiag(diag, "wallet.privacy.start", { ghostWallet: desired });
122813
- const current = typeof wallet.getWalletSettings === "function" ? await wallet.getWalletSettings() : null;
122814
- if (closed || requestRevision !== revision || current?.privateEnabled === true === desired) {
122815
- markDone(diag, "wallet.privacy", startedAt, { changed: false, cancelled: closed || requestRevision !== revision });
122816
- return;
122817
- }
122818
123069
  await wallet.setPrivacyEnabled(desired);
122819
123070
  markDone(diag, "wallet.privacy", startedAt, { changed: true });
122820
123071
  }).catch((error) => {
@@ -123664,7 +123915,7 @@ function adaptiveClaimBatchSize(maxBatchSize, queueDepth) {
123664
123915
  }
123665
123916
  return 1;
123666
123917
  }
123667
- function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, transferStore, appState = null, deferBackgroundWork = null, backgroundSignal = null, diag }) {
123918
+ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, eagerCacheHistory = true, transferStore, appState = null, deferBackgroundWork = null, backgroundSignal = null, diag }) {
123668
123919
  if (!transferStore || typeof transferStore.subscribe !== "function" || typeof transferStore.getSnapshot !== "function" || typeof transferStore.getCacheState !== "function" || typeof transferStore.setHistory !== "function") {
123669
123920
  throw new Error("createWalletTransferSession requires transferStore");
123670
123921
  }
@@ -123678,6 +123929,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123678
123929
  const initialTransfers = canUseInitialStore ? transferStore.getTransfers() : [];
123679
123930
  const initialHistoryTransfers = canUseInitialStore ? transferStore.getHistoryTransfers() : [];
123680
123931
  const initialTxReady = canUseInitialStore && (initialStoreSnapshot.ready || initialTransfers.length > 0 || initialHistoryTransfers.length > 0);
123932
+ const initialCacheReady = initialTxReady && (eagerCacheHistory === false || initialStoreSnapshot.serverEndReached === true && initialHistoryTransfers.length >= initialStoreSnapshot.historyCount);
123681
123933
  const listeners = new Set;
123682
123934
  let isTxLoading = false;
123683
123935
  let publishedSnapshot = null;
@@ -125394,6 +125646,9 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125394
125646
  if (!wallet || !localCache || !walletPKRef.current) {
125395
125647
  return;
125396
125648
  }
125649
+ if (initialCacheReady) {
125650
+ return;
125651
+ }
125397
125652
  const hydrateKey = `${localCache.id || "cache"}:${walletPKRef.current}`;
125398
125653
  if (cacheHydrateKeyRef.current === hydrateKey) {
125399
125654
  return;
@@ -125418,7 +125673,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125418
125673
  return;
125419
125674
  }
125420
125675
  const hydratedHead = applyCachedTransferState(cachedHead, { label: "wallet.provider.cache.hydrate", publish: true, startedAt: hydrateStartedAt });
125421
- if (cancelled)
125676
+ if (cancelled || !eagerCacheHistory)
125422
125677
  return;
125423
125678
  await loadFullTransferCache({ label: "wallet.provider.cache.fullHydrate", publish: !hydratedHead });
125424
125679
  }).catch((error) => {
@@ -125827,6 +126082,7 @@ function createWalletSession({
125827
126082
  pendingClaimBatchSize = null,
125828
126083
  activeTransferRefreshMs = null,
125829
126084
  eagerOutgoingStatusRefresh = false,
126085
+ eagerCacheHistory = true,
125830
126086
  sentTransferRefreshDelayMs = null,
125831
126087
  deferBackgroundWork = null,
125832
126088
  appState = null,
@@ -125855,6 +126111,7 @@ function createWalletSession({
125855
126111
  claimIncomingTransfers,
125856
126112
  pendingClaimBatchSize,
125857
126113
  eagerOutgoingStatusRefresh,
126114
+ eagerCacheHistory,
125858
126115
  transferStore,
125859
126116
  appState,
125860
126117
  deferBackgroundWork,
@@ -125965,7 +126222,7 @@ function createWalletSession({
125965
126222
  balanceReady: balanceState.balanceReady,
125966
126223
  isBalanceLoading: balanceState.isBalanceLoading,
125967
126224
  isWalletDataReady: true,
125968
- isWalletDataLoaded: balanceState.balanceReady && currentTransferState.txReady,
126225
+ isWalletDataLoaded: balanceState.balanceReady || currentTransferState.txReady,
125969
126226
  refresh: claims.refresh,
125970
126227
  requestTransferDiscoveryHotWindow: polling.requestTransferDiscoveryHotWindow,
125971
126228
  claimDeposits: claims.claimDeposits,
@@ -169907,7 +170164,12 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169907
170164
  "Content-Type": upload?.metadata?.contentType || "application/octet-stream"
169908
170165
  };
169909
170166
  if (typeof uploadSignedStorageBytes === "function") {
169910
- await uploadSignedStorageBytes(url, upload?.body, { ...signed, method, headers, metadata: upload?.metadata || {}, path: upload?.path || "" });
170167
+ await uploadSignedStorageBytes(url, upload?.body, { ...signed, method, headers, metadata: upload?.metadata || {}, path: upload?.path || "" }).catch((error) => {
170168
+ if (error?.status === 412 && (headers["X-Goog-If-Generation-Match"] === "0" || headers["x-goog-if-generation-match"] === "0")) {
170169
+ return true;
170170
+ }
170171
+ throw error;
170172
+ });
169911
170173
  return true;
169912
170174
  }
169913
170175
  const payload = typeof Blob !== "undefined" && upload?.body instanceof Blob ? upload.body : toBytes2(upload?.body, "upload bytes");
@@ -169916,6 +170178,9 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169916
170178
  headers,
169917
170179
  body: payload
169918
170180
  });
170181
+ if (response.status === 412 && (headers["X-Goog-If-Generation-Match"] === "0" || headers["x-goog-if-generation-match"] === "0")) {
170182
+ return true;
170183
+ }
169919
170184
  if (!response.ok) {
169920
170185
  const error = new Error(`signed upload failed (${response.status || 0})`);
169921
170186
  error.status = response.status || 0;
@@ -170238,16 +170503,11 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170238
170503
  await deleteObject(ref(targetStorage, avatarPath(uid)));
170239
170504
  return true;
170240
170505
  }
170241
- async function writeProfileWalletPK(walletPK, { network } = {}) {
170242
- if (!walletPK)
170243
- throw new Error("wallet public key required");
170244
- await callVaultFunction("setWalletPK", VAULT_SIGNATURE_PURPOSES.SET_WALLET_IDENTITY, { walletPK, network });
170245
- return true;
170246
- }
170247
- async function writeProfileChatPK(chatPK) {
170248
- if (!chatPK)
170249
- throw new Error("chat public key required");
170250
- await callVaultFunction("setChatPK", VAULT_SIGNATURE_PURPOSES.SET_CHAT_IDENTITY, { chatPK });
170506
+ async function writeProfileIdentity(identity) {
170507
+ if (!identity || typeof identity !== "object" || Array.isArray(identity)) {
170508
+ throw new Error("profile identity required");
170509
+ }
170510
+ await callVaultFunction("setIdentities", VAULT_SIGNATURE_PURPOSES.SET_IDENTITIES, identity);
170251
170511
  return true;
170252
170512
  }
170253
170513
  async function getUsername(username) {
@@ -170297,9 +170557,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170297
170557
  }
170298
170558
  return openSettings(key, uid, readCloudBytes(saved, "settings body"));
170299
170559
  }
170300
- const settings = normalizeSettings(defaultSettings);
170301
- await callVaultFunction("setSettings", VAULT_SIGNATURE_PURPOSES.SET_SETTINGS, { settings: bytesBase64(await sealSettings(key, uid, settings)) });
170302
- return settings;
170560
+ return normalizeSettings(defaultSettings);
170303
170561
  }
170304
170562
  async function writeSettings(uid, settings, { currentSettings, key } = {}) {
170305
170563
  requireUid(uid);
@@ -170437,20 +170695,12 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170437
170695
  const snapshot = await getDocs(roleQuery);
170438
170696
  return peerRecordsFromSnapshot(snapshot);
170439
170697
  }
170440
- async function uploadChatMedia(upload) {
170698
+ async function uploadMedia(upload) {
170441
170699
  const path = upload?.path;
170442
170700
  if (!path)
170443
170701
  throw new Error("media path required");
170444
- getChatMediaFileRef(path);
170445
- await uploadStorageFile(path, upload?.body, upload?.metadata || {});
170446
- return true;
170447
- }
170448
- async function uploadSharedMedia(upload) {
170449
- const path = upload?.path;
170450
- if (!path)
170451
- throw new Error("shared media path required");
170452
- const signed = await callFunction("reserveSharedMediaUpload", {
170453
- sharedId: upload?.sharedId,
170702
+ getMediaFileRef(path);
170703
+ const signed = await callFunction("reserveMediaUpload", {
170454
170704
  path,
170455
170705
  size: uploadByteSize(upload?.body),
170456
170706
  contentType: upload?.metadata?.contentType || "application/octet-stream"
@@ -170458,6 +170708,14 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170458
170708
  await uploadSignedStorageFile(upload, signed?.upload);
170459
170709
  return true;
170460
170710
  }
170711
+ async function uploadChatMedia(upload) {
170712
+ getChatMediaFileRef(upload?.path);
170713
+ return uploadMedia(upload);
170714
+ }
170715
+ async function uploadSharedMedia(upload) {
170716
+ getSharedMediaFileRef(upload?.path);
170717
+ return uploadMedia(upload);
170718
+ }
170461
170719
  async function readChatMedia(path) {
170462
170720
  if (!path)
170463
170721
  throw new Error("media path required");
@@ -171187,16 +171445,13 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
171187
171445
  },
171188
171446
  profile: {
171189
171447
  watch: watchProfile,
171448
+ identity: {
171449
+ write: writeProfileIdentity
171450
+ },
171190
171451
  avatar: {
171191
171452
  write: writeProfileAvatar,
171192
171453
  upload: uploadProfileAvatar,
171193
171454
  delete: deleteProfileAvatar
171194
- },
171195
- walletpk: {
171196
- write: writeProfileWalletPK
171197
- },
171198
- chatpk: {
171199
- write: writeProfileChatPK
171200
171455
  }
171201
171456
  },
171202
171457
  private: {
@@ -173948,7 +174203,7 @@ function createRuntimeProductActions({
173948
174203
  if (Object.prototype.hasOwnProperty.call(patch, "walletNetwork") && nextNetwork !== previousNetwork) {
173949
174204
  await lockVault();
173950
174205
  if (nextNetwork)
173951
- setNetwork(nextNetwork);
174206
+ await setNetwork(nextNetwork);
173952
174207
  }
173953
174208
  return { ...next, autolock: { ...next.autolock }, locked: nextNetwork !== previousNetwork };
173954
174209
  }
@@ -174577,7 +174832,8 @@ function createRuntimeChatActions({ getRuntime, getSession, resolvePeer }) {
174577
174832
  const updated = await target.runtime.chat.getSnapshot().updateMessage(target.chat.id, target.message.id, message, target.profile.chatPK);
174578
174833
  return {
174579
174834
  chat: simplifyAccountChat(target.chat, target.profile),
174580
- message: simplifyAccountMessage(updated || message, target.chat, target.session.chatPK, options2)
174835
+ message: simplifyAccountMessage(message, target.chat, target.session.chatPK, options2),
174836
+ actionId: cleanText(updated?.msgId || updated?.cid) || null
174581
174837
  };
174582
174838
  }
174583
174839
  async function deleteChat(peer, options2 = {}) {
@@ -175166,9 +175422,17 @@ class VeylHeadlessClient {
175166
175422
  resolvePeer: (peer, resolveOptions) => this.resolvePeer(peer, resolveOptions),
175167
175423
  chat: this.runtimeChat,
175168
175424
  lockVault: () => this.lockVault(),
175169
- setNetwork: (network) => {
175170
- this.network = network;
175171
- this.userOwner.setNetwork(network);
175425
+ setNetwork: async (network) => {
175426
+ const nextNetwork = String(network || "").toUpperCase();
175427
+ this.network = nextNetwork;
175428
+ this.userOwner.setNetwork(nextNetwork);
175429
+ if (this.profileData) {
175430
+ await this.saveProfile({
175431
+ ...this.profileData,
175432
+ network: nextNetwork,
175433
+ walletPK: null
175434
+ });
175435
+ }
175172
175436
  },
175173
175437
  runPasskeyBrowserFlow,
175174
175438
  webUrl: options2.webUrl,
@@ -175281,7 +175545,7 @@ class VeylHeadlessClient {
175281
175545
  username: profile.username,
175282
175546
  profile: profile.profile,
175283
175547
  credentialId: profile.credentialId,
175284
- network: profile.network || this.network,
175548
+ network: this.session?.network || profile.network || this.network,
175285
175549
  hasVault: profile.hasVault === true,
175286
175550
  hasVaultSecret: !!profile.vaultSecret,
175287
175551
  walletPK: profile.walletPK || null,
@@ -175642,10 +175906,9 @@ class VeylHeadlessClient {
175642
175906
  vaultCrypto: headlessVaultCrypto,
175643
175907
  bootWallet: (walletMnemonic, currentUser, { network }) => bootWallet(walletMnemonic, currentUser, {
175644
175908
  SparkWallet: SparkWalletNodeJS,
175645
- cloud: this.cloud,
175646
175909
  network
175647
175910
  }),
175648
- bootChat: (chatSeed, currentUser) => bootChat(chatSeed, currentUser, { cloud: this.cloud }),
175911
+ bootChat: (chatSeed, currentUser) => bootChat(chatSeed, currentUser),
175649
175912
  openLocalCache: (cacheSeed, { uid, network }) => openHeadlessLocalDataCache(cacheSeed, {
175650
175913
  homeDir: this.store.dir,
175651
175914
  uid,
@@ -175656,6 +175919,7 @@ class VeylHeadlessClient {
175656
175919
  this.userOwner.setNetwork(network);
175657
175920
  }
175658
175921
  });
175922
+ await opened.walletReady;
175659
175923
  this.userSnapshot = this.userOwner.getSnapshot();
175660
175924
  } catch (error) {
175661
175925
  if (opened)
@@ -175690,6 +175954,7 @@ class VeylHeadlessClient {
175690
175954
  ...profile,
175691
175955
  hasVault: true,
175692
175956
  vaultSecret: options2.saveSecret === false ? null : vaultSecret,
175957
+ network: session.network,
175693
175958
  walletPK: session.walletPK,
175694
175959
  chatPK: session.chatPK,
175695
175960
  vaultCreatedAt: Date.now()
@@ -175734,6 +175999,7 @@ class VeylHeadlessClient {
175734
175999
  ...profile,
175735
176000
  hasVault: true,
175736
176001
  vaultSecret: options2.saveSecret === false ? profile.vaultSecret || null : profile.vaultSecret || vaultSecret,
176002
+ network: session.network,
175737
176003
  walletPK: session.walletPK,
175738
176004
  chatPK: session.chatPK,
175739
176005
  lastUnlockedAt: Date.now()