@glyphteck/veyl 0.32.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
  }
@@ -446,6 +447,34 @@ var init_legal = __esm(() => {
446
447
  };
447
448
  });
448
449
 
450
+ // ../../shared/actioncopy.js
451
+ var ACCOUNT_DELETE_PREPARE = "you should export or empty your wallet first.", ACCOUNT_DELETE_CONDITION = "if you delete your account,", ACCOUNT_DELETE_CONSEQUENCE = "you will permanently lose access to your funds and chats.", PASSKEY_DELETE_PENDING = "deleting a pending link disables it.", PASSKEY_DELETE_VERIFIED = "deleting a verified passkey will log out any session currently using it.", PASSKEY_DELETE_LIMITS = "you can’t delete the passkey used by this session or the account’s last passkey.", ACCOUNT_DELETE_COPY, BLOCK_USER_BODY = "they will no longer be able to message you, and this chat will be permanently deleted.", UNBLOCK_USER_BODY = "they will be able to message you again.", CHAT_DELETE_BODY = "this chat will be permanently deleted.", PASSKEY_DELETE_COPY, WALLET_EXPORT_COPY, WITHDRAWAL_COPY, LOGOUT_ALL_DEVICES_BODY = "This will sign your account out on every connected device. If you suspect someone else has access, logging out may not be enough: they may already have copied your wallet recovery phrase or other sensitive information. Move your funds to a safe wallet only you control as soon as possible, then remove sensitive information from this account. Information already copied to another device cannot be erased remotely.";
452
+ var init_actioncopy = __esm(() => {
453
+ ACCOUNT_DELETE_COPY = Object.freeze({
454
+ prepare: ACCOUNT_DELETE_PREPARE,
455
+ condition: ACCOUNT_DELETE_CONDITION,
456
+ consequence: ACCOUNT_DELETE_CONSEQUENCE,
457
+ body: `${ACCOUNT_DELETE_PREPARE} ${ACCOUNT_DELETE_CONDITION} ${ACCOUNT_DELETE_CONSEQUENCE}`
458
+ });
459
+ PASSKEY_DELETE_COPY = Object.freeze({
460
+ pendingBody: PASSKEY_DELETE_PENDING,
461
+ body: PASSKEY_DELETE_VERIFIED,
462
+ description: `${PASSKEY_DELETE_PENDING} ${PASSKEY_DELETE_VERIFIED}`,
463
+ infoBody: `${PASSKEY_DELETE_PENDING} ${PASSKEY_DELETE_LIMITS} ${PASSKEY_DELETE_VERIFIED}`
464
+ });
465
+ WALLET_EXPORT_COPY = Object.freeze({
466
+ networkLead: "this is not a bitcoin wallet.",
467
+ networkBody: "you cannot use it like a normal bitcoin wallet. you can only use it with the spark network: with a new account on this platform, on a different platform that uses spark wallets, or directly through the spark sdk.",
468
+ withdrawBody: "if you do not want to use this account anymore, withdraw your funds back to a bitcoin wallet instead.",
469
+ secretBody: "the mnemonic gives full control of this Spark wallet. reveal it only somewhere private."
470
+ });
471
+ WITHDRAWAL_COPY = Object.freeze({
472
+ feeBody: "you can withdraw your funds back to any bitcoin address. bitcoin transactions are not free. validators need to get paid.",
473
+ reviewBody: "veyl gets a live Spark withdrawal quote after you press withdraw. review the destination address, the amount leaving your wallet, the quoted fee, and the amount that reaches the address before confirming.",
474
+ irreversibleBody: "once confirmed, bitcoin withdrawals are irreversible."
475
+ });
476
+ });
477
+
449
478
  // src/commands.js
450
479
  import { Buffer as Buffer2 } from "node:buffer";
451
480
  import { chmod, readFile, writeFile } from "node:fs/promises";
@@ -613,6 +642,7 @@ function cliHelpLines() {
613
642
  }
614
643
  var stringProp, numberProp, booleanProp, objectProp, stringArrayProp, MIME_BY_EXTENSION, TYPE_BY_MIME, optionsOnly = (_args, context) => ({ ...context.commandOptions }), firstArg = (key) => (args, context) => ({ ...context.commandOptions, [key]: args[0] }), peerInput, peerMessageInput = (args, context) => ({ ...context.commandOptions, peer: args[0], message: args.slice(1).join(" ") }), peerMessageIdInput = (args, context) => ({ ...context.commandOptions, peer: args[0], messageId: args[1] }), peerCidInput = (args, context) => ({ ...context.commandOptions, peer: args[0], cid: args[1] }), peerMessageActionInput = (args, context) => ({ ...context.commandOptions, peer: args[0], messageId: args[1], message: args.slice(2).join(" ") }), peerEmojiInput = (args, context) => ({ ...context.commandOptions, peer: args[0], messageId: args[1], emoji: args.slice(2).join(" ") }), peerSatsInput = (args, context) => ({ ...context.commandOptions, peer: args[0], sats: args[1] }), invoiceInput, requestInput, idInput, withdrawalInput = (args, context) => ({ ...context.commandOptions, address: args[0], sats: args[1] }), inviteLinkInput = (args, context) => ({ ...context.commandOptions, kind: args[0], sats: args[1] }), HEADLESS_COMMANDS, commandByName, cliPaths, MCP_TOOLS, groups, COMMANDS;
615
644
  var init_commands = __esm(() => {
645
+ init_actioncopy();
616
646
  stringProp = Object.freeze({ type: "string" });
617
647
  numberProp = Object.freeze({ type: "number" });
618
648
  booleanProp = Object.freeze({ type: "boolean" });
@@ -689,12 +719,12 @@ var init_commands = __esm(() => {
689
719
  invokeCli: async (client, input, context) => client.account.acceptTerms({ ...input, termsVersion: await context.acceptTerms(input.termsVersion) })
690
720
  }),
691
721
  command({ name: "account_logout", group: "account", apiName: "logout", description: "Lock and sign out this runtime.", usage: "account logout", cliInput: optionsOnly, access: "write", invoke: (client) => client.account.logout() }),
692
- command({ name: "account_logout_all", group: "account", apiName: "logoutAll", description: "Revoke all product auth sessions.", usage: "account logout-all", cliInput: optionsOnly, access: "destructive", invoke: (client) => client.account.logoutAll() }),
693
- command({ name: "account_delete", group: "account", apiName: "delete", description: "Delete the account, chats, and local profile.", properties: { confirm: booleanProp }, required: ["confirm"], usage: "account delete", cliInput: optionsOnly, access: "destructive", invoke: (client, input) => client.account.delete(input) }),
722
+ command({ name: "account_logout_all", group: "account", apiName: "logoutAll", description: LOGOUT_ALL_DEVICES_BODY, usage: "account logout-all", cliInput: optionsOnly, access: "destructive", invoke: (client) => client.account.logoutAll() }),
723
+ command({ name: "account_delete", group: "account", apiName: "delete", description: ACCOUNT_DELETE_COPY.body, properties: { confirm: booleanProp }, required: ["confirm"], usage: "account delete", cliInput: optionsOnly, access: "destructive", invoke: (client, input) => client.account.delete(input) }),
694
724
  command({ name: "vault_create", group: "vault", apiName: "create", description: "Create and upload a locally encrypted vault.", properties: { secret: stringProp, saveSecret: booleanProp }, usage: "vault create", cliInput: vaultInput, access: "write", secret: true, invoke: (client, input) => client.vault.create(input) }),
695
725
  command({ name: "vault_unlock", group: "vault", apiName: "unlock", description: "Unlock the local vault.", properties: { secret: stringProp }, usage: "vault unlock", cliInput: vaultInput, access: "write", secret: true, invoke: (client, input) => client.vault.unlock(input) }),
696
726
  command({ name: "vault_lock", group: "vault", apiName: "lock", description: "Lock wallet and chat state.", usage: "vault lock", cliInput: optionsOnly, access: "write", invoke: (client) => client.vault.lock() }),
697
- command({ name: "vault_export", group: "vault", apiName: "export", description: "Export the Spark wallet mnemonic.", properties: { secret: stringProp }, usage: "vault export", cliInput: vaultInput, mcp: false, secret: true, invoke: (client, input) => client.vault.export(input) }),
727
+ command({ name: "vault_export", group: "vault", apiName: "export", description: WALLET_EXPORT_COPY.secretBody, properties: { secret: stringProp }, usage: "vault export", cliInput: vaultInput, mcp: false, secret: true, invoke: (client, input) => client.vault.export(input) }),
698
728
  command({ name: "profile_show", group: "profile", apiName: "show", description: "Show the public account profile.", usage: "profile show", cliInput: optionsOnly, invoke: (client) => client.profile.show() }),
699
729
  command({
700
730
  name: "profile_avatar_set",
@@ -720,8 +750,8 @@ var init_commands = __esm(() => {
720
750
  command({ name: "peers_search", group: "peers", apiName: "search", description: "Search public Veyl profiles.", properties: { query: stringProp, count: numberProp }, required: ["query"], usage: "peers search <query>", cliInput: (args, context) => ({ ...context.commandOptions, query: args.join(" ") }), invoke: (client, input) => client.peers.search(input.query, input) }),
721
751
  command({ name: "peers_list", group: "peers", apiName: "list", description: "List recent peers.", properties: { count: numberProp, recent: booleanProp }, usage: "peers list", cliInput: optionsOnly, invoke: (client, input) => client.peers.list(input) }),
722
752
  command({ name: "peers_blocked", group: "peers", apiName: "blocked", description: "List blocked peers.", usage: "peers blocked", cliInput: optionsOnly, invoke: (client) => client.peers.blocked() }),
723
- command({ name: "peers_block", group: "peers", apiName: "block", description: "Block a peer and hide their local chat.", properties: { peer: stringProp }, required: ["peer"], usage: "peers block @name", cliInput: peerInput, access: "destructive", invoke: (client, input) => client.peers.block(input.peer) }),
724
- command({ name: "peers_unblock", group: "peers", apiName: "unblock", description: "Unblock a peer.", properties: { peer: stringProp }, required: ["peer"], usage: "peers unblock @name", cliInput: peerInput, access: "write", invoke: (client, input) => client.peers.unblock(input.peer) }),
753
+ command({ name: "peers_block", group: "peers", apiName: "block", description: BLOCK_USER_BODY, properties: { peer: stringProp }, required: ["peer"], usage: "peers block @name", cliInput: peerInput, access: "destructive", invoke: (client, input) => client.peers.block(input.peer) }),
754
+ command({ name: "peers_unblock", group: "peers", apiName: "unblock", description: UNBLOCK_USER_BODY, properties: { peer: stringProp }, required: ["peer"], usage: "peers unblock @name", cliInput: peerInput, access: "write", invoke: (client, input) => client.peers.unblock(input.peer) }),
725
755
  command({ name: "chat_list", group: "chat", apiName: "list", description: "List chats.", properties: { count: numberProp }, usage: "chat list", cliInput: optionsOnly, invoke: (client, input) => client.chat.list(input) }),
726
756
  command({ name: "chat_messages", group: "chat", apiName: "messages", clientMethods: ["read"], description: "Read messages and mark the latest peer message as read.", properties: { peer: stringProp, count: numberProp }, required: ["peer"], usage: "chat messages @name", cliInput: peerInput, access: "write", invoke: (client, input) => client.chat.read(input.peer, input) }),
727
757
  command({ name: "chat_mark_read", group: "chat", apiName: "markRead", description: "Mark the latest peer message as read.", properties: { peer: stringProp }, required: ["peer"], usage: "chat mark-read @name", cliInput: peerInput, access: "write", invoke: (client, input) => client.chat.markRead(input.peer, input) }),
@@ -734,7 +764,7 @@ var init_commands = __esm(() => {
734
764
  command({ name: "chat_unsave", group: "chat", apiName: "unsave", description: "Return one message to temporary retention.", properties: { peer: stringProp, messageId: stringProp }, required: ["peer", "messageId"], usage: "chat unsave @name message-id", cliInput: peerMessageIdInput, access: "write", invoke: (client, { peer, messageId, ...options }) => client.chat.unsave(peer, messageId, options) }),
735
765
  command({ name: "chat_edit", group: "chat", apiName: "edit", clientMethods: ["update"], description: "Edit a text message you sent.", properties: { peer: stringProp, messageId: stringProp, message: stringProp }, required: ["peer", "messageId", "message"], usage: "chat edit @name message-id message", cliInput: peerMessageActionInput, access: "write", invoke: (client, { peer, messageId, message, ...options }) => client.chat.update(peer, messageId, message, options) }),
736
766
  command({ name: "chat_delete_message", group: "chat", apiName: "deleteMessage", clientMethods: ["delete"], description: "Delete one message immediately.", properties: { peer: stringProp, messageId: stringProp }, required: ["peer", "messageId"], usage: "chat delete-message @name message-id", cliInput: peerMessageIdInput, access: "destructive", invoke: (client, { peer, messageId, ...options }) => client.chat.delete(peer, messageId, options) }),
737
- command({ name: "chat_delete", group: "chat", apiName: "deleteChat", description: "Delete one chat.", properties: { peer: stringProp, cleanup: booleanProp }, required: ["peer"], usage: "chat delete @name", cliInput: peerInput, access: "destructive", invoke: (client, input) => client.chat.deleteChat(input.peer, input) }),
767
+ command({ name: "chat_delete", group: "chat", apiName: "deleteChat", description: CHAT_DELETE_BODY, properties: { peer: stringProp, cleanup: booleanProp }, required: ["peer"], usage: "chat delete @name", cliInput: peerInput, access: "destructive", invoke: (client, input) => client.chat.deleteChat(input.peer, input) }),
738
768
  command({ name: "chat_retention", group: "chat", apiName: "retention", description: "Set chat retention to seen or 24h.", properties: { peer: stringProp, retention: stringProp }, required: ["peer", "retention"], usage: "chat retention @name seen|24h", cliInput: (args, context) => ({ ...context.commandOptions, peer: args[0], retention: args[1] }), access: "write", invoke: (client, input) => client.chat.retention(input.peer, input.retention, input) }),
739
769
  command({
740
770
  name: "chat_send_file",
@@ -819,12 +849,12 @@ var init_commands = __esm(() => {
819
849
  command({ name: "lightning_send", group: "lightning", apiName: "send", description: "Read a Lightning send request.", properties: { id: stringProp }, required: ["id"], usage: "lightning send id", cliInput: idInput, money: true, invoke: (client, input) => client.lightning.send(input.id, input) }),
820
850
  command({ name: "withdrawal_quote", group: "withdrawal", apiName: "quote", description: "Quote an on-chain withdrawal.", properties: { address: stringProp, sats: numberProp }, required: ["address", "sats"], usage: "withdrawal quote address sats", cliInput: withdrawalInput, access: "write", money: true, invoke: (client, input) => client.withdrawal.quote(input.address, input.sats, input) }),
821
851
  command({ name: "withdrawal_prepare", group: "withdrawal", apiName: "prepare", description: "Prepare an on-chain withdrawal review.", properties: { address: stringProp, sats: numberProp, speed: stringProp, deductFeeFromWithdrawalAmount: booleanProp }, required: ["address", "sats"], usage: "withdrawal prepare address sats", cliInput: withdrawalInput, access: "write", money: true, invoke: (client, input) => client.withdrawal.prepare(input.address, input.sats, input) }),
822
- command({ name: "withdrawal_confirm", group: "withdrawal", apiName: "confirm", description: "Confirm an on-chain withdrawal.", properties: { address: stringProp, sats: numberProp, speed: stringProp, feeQuoteId: stringProp, feeAmountSats: numberProp, deductFeeFromWithdrawalAmount: booleanProp }, required: ["address", "sats"], usage: "withdrawal confirm address sats", cliInput: withdrawalInput, access: "destructive", money: true, invoke: (client, input) => client.withdrawal.confirm(input.address, input.sats, input) }),
852
+ command({ name: "withdrawal_confirm", group: "withdrawal", apiName: "confirm", description: WITHDRAWAL_COPY.irreversibleBody, properties: { address: stringProp, sats: numberProp, speed: stringProp, feeQuoteId: stringProp, feeAmountSats: numberProp, deductFeeFromWithdrawalAmount: booleanProp }, required: ["address", "sats"], usage: "withdrawal confirm address sats", cliInput: withdrawalInput, access: "destructive", money: true, invoke: (client, input) => client.withdrawal.confirm(input.address, input.sats, input) }),
823
853
  command({ name: "invite_link", group: "invite", apiName: "link", description: "Create a join, chat, send, or request invite link.", properties: { kind: stringProp, sats: numberProp }, usage: "invite link [join|chat|send|request] [sats]", cliInput: inviteLinkInput, invoke: (client, input) => client.invite.link(input) }),
824
854
  command({ name: "invite_read", group: "invite", apiName: "read", description: "Parse a Veyl invite URL.", properties: { value: stringProp }, required: ["value"], usage: "invite read url", cliInput: firstArg("value"), invoke: (client, input) => client.invite.read(input.value) }),
825
855
  command({ name: "passkeys_list", group: "passkeys", apiName: "list", description: "List passkeys and pending links.", usage: "passkeys list", cliInput: optionsOnly, invoke: (client, input) => client.passkeys.list(input) }),
826
856
  command({ name: "passkeys_link", group: "passkeys", apiName: "link", clientMethods: ["createLink"], description: "Create a one-time passkey link.", properties: { webUrl: stringProp }, usage: "passkeys link", cliInput: optionsOnly, mcp: false, access: "write", secret: true, invoke: (client, input) => client.passkeys.createLink(input) }),
827
- command({ name: "passkeys_delete", group: "passkeys", apiName: "delete", description: "Delete a pending link or verified passkey.", properties: { id: stringProp, webUrl: stringProp }, required: ["id"], usage: "passkeys delete id", cliInput: (args, context) => ({ ...context.commandOptions, id: args[0], webUrl: context.options.webUrl, onUrl: context.onPasskeyUrl }), access: "destructive", invoke: (client, input) => client.passkeys.delete(input.id, input) }),
857
+ command({ name: "passkeys_delete", group: "passkeys", apiName: "delete", description: PASSKEY_DELETE_COPY.description, properties: { id: stringProp, webUrl: stringProp }, required: ["id"], usage: "passkeys delete id", cliInput: (args, context) => ({ ...context.commandOptions, id: args[0], webUrl: context.options.webUrl, onUrl: context.onPasskeyUrl }), access: "destructive", invoke: (client, input) => client.passkeys.delete(input.id, input) }),
828
858
  command({ name: "support_feedback", group: "support", apiName: "feedback", description: "Send product feedback.", properties: { message: stringProp, type: stringProp, context: stringProp }, required: ["message"], usage: "support feedback message", cliInput: (args, context) => ({ ...context.commandOptions, message: args.join(" ") }), access: "write", invoke: (client, input) => client.support.feedback(input.message, input) }),
829
859
  command({ name: "support_bug", group: "support", apiName: "bug", description: "Submit a bug report.", properties: { message: stringProp }, required: ["message"], usage: "support bug message", cliInput: (args, context) => ({ ...context.commandOptions, message: args.join(" ") }), access: "write", invoke: (client, input) => client.support.bug(input.message, input) }),
830
860
  command({ name: "support_report", group: "support", apiName: "report", description: "Report a peer or one harmful message.", properties: { peer: stringProp, messageId: stringProp, note: stringProp, includeAttachment: booleanProp }, required: ["peer"], usage: "support report @name [message-id]", cliInput: (args, context) => ({ ...context.commandOptions, peer: args[0], messageId: args[1] }), access: "write", invoke: (client, input) => client.support.report(input.peer, input) }),
@@ -979,7 +1009,7 @@ var package_default;
979
1009
  var init_package = __esm(() => {
980
1010
  package_default = {
981
1011
  name: "veyl",
982
- version: "0.32.1",
1012
+ version: "1.0.0",
983
1013
  private: true,
984
1014
  license: "Apache-2.0",
985
1015
  workspaces: {
@@ -995,16 +1025,16 @@ var init_package = __esm(() => {
995
1025
  "@noble/curves": "^2.2.0",
996
1026
  "@noble/hashes": "^2.2.0",
997
1027
  "@scure/bip39": "^2.2.0",
998
- "@tailwindcss/postcss": "^4.3.2",
1028
+ "@tailwindcss/postcss": "^4.3.3",
999
1029
  bech32: "^2.0.0",
1000
1030
  firebase: "^12.16.0",
1001
1031
  "lucide-react": "^1.24.0",
1002
1032
  next: "16.3.0-preview.6",
1003
1033
  "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",
1034
+ postcss: "^8.5.22",
1035
+ react: "19.2.8",
1036
+ "react-dom": "19.2.8",
1037
+ tailwindcss: "^4.3.3",
1008
1038
  "tw-animate-css": "^1.4.0"
1009
1039
  }
1010
1040
  },
@@ -104534,7 +104564,7 @@ var init_seed = __esm(() => {
104534
104564
  });
104535
104565
 
104536
104566
  // ../../shared/config.js
104537
- 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;
104567
+ 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;
104538
104568
  var init_config = __esm(() => {
104539
104569
  MINUTE_MS = 60 * MS_PER_SECOND;
104540
104570
  HOUR_MS = 60 * MINUTE_MS;
@@ -104545,7 +104575,7 @@ var init_config = __esm(() => {
104545
104575
  LOCAL_PROFILE_CACHE_MAX_AGE_MS = 30 * DAY_MS;
104546
104576
  LOCAL_AVATAR_CACHE_MAX_BYTES = 128 * MIB_BYTES;
104547
104577
  LOCAL_AVATAR_CACHE_MAX_AGE_MS = 30 * DAY_MS;
104548
- AVATAR_IMAGE_MAX_BYTES = 8 * MIB_BYTES;
104578
+ AVATAR_IMAGE_MAX_BYTES = 256 * KIB_BYTES;
104549
104579
  CHAT_READY_DOWNLOAD_MAX_BYTES = 8 * MIB_BYTES;
104550
104580
  CHAT_MESSAGE_FILE_CACHE_MAX_BYTES = 64 * MIB_BYTES;
104551
104581
  CHAT_UNSAVED_TTL_MS = CHAT_UNSAVED_TTL_DAYS * DAY_MS;
@@ -105980,20 +106010,17 @@ function expectedSigningKeys(pair, signingKeysByChatKey) {
105980
106010
  }
105981
106011
  return values;
105982
106012
  }
105983
- async function verifyExpectedSignatureBeforeOpen(pair, sig, bytes, options = {}) {
106013
+ async function preverifyExpectedSigner(pair, sig, bytes, options = {}) {
105984
106014
  if (!options?.crypto?.preverifyBeforeOpen) {
105985
- return true;
105986
- }
105987
- const signingKeysByChatKey = expectedSigningKeys(pair, options?.signingKeysByChatKey);
105988
- if (signingKeysByChatKey.length < 2) {
105989
- return true;
106015
+ return "";
105990
106016
  }
105991
- for (const signingPublicKey of signingKeysByChatKey) {
106017
+ const signingPublicKeys = expectedSigningKeys(pair, options?.signingKeysByChatKey);
106018
+ for (const signingPublicKey of signingPublicKeys) {
105992
106019
  if (await verifyMsgSignature(signingPublicKey, sig, bytes, options)) {
105993
- return true;
106020
+ return signingPublicKey;
105994
106021
  }
105995
106022
  }
105996
- return false;
106023
+ return null;
105997
106024
  }
105998
106025
  async function sealMsg(pair, message, options = {}) {
105999
106026
  const head = getHead(pair, message);
@@ -106014,14 +106041,16 @@ async function openMsg(pair, data, options = {}) {
106014
106041
  const { suite, flags, nonce, sig, ct } = unpackMsgBody(data.body);
106015
106042
  const aad = getMsgAad(pair, head, suite, flags);
106016
106043
  const signatureInput = getSignatureInput(aad, nonce, ct);
106017
- if (!await verifyExpectedSignatureBeforeOpen(pair, sig, signatureInput, options)) {
106044
+ const preverifiedSigner = await preverifyExpectedSigner(pair, sig, signatureInput, options);
106045
+ if (preverifiedSigner == null) {
106018
106046
  throw new Error("invalid chat action signature");
106019
106047
  }
106020
106048
  const plaintext = await openMsgPlaintext(pair, nonce, ct, aad, options);
106021
106049
  try {
106022
106050
  const action = JSON.parse(decoder.decode(plaintext));
106023
106051
  const normalized = normalizeOpenedAction(pair, head, action, options);
106024
- if (!await verifyMsgSignature(normalized.signingPublicKey, sig, signatureInput, options)) {
106052
+ const validSignature = preverifiedSigner ? normalized.signingPublicKey === preverifiedSigner : await verifyMsgSignature(normalized.signingPublicKey, sig, signatureInput, options);
106053
+ if (!validSignature) {
106025
106054
  throw new Error("invalid chat action signature");
106026
106055
  }
106027
106056
  return {
@@ -106095,40 +106124,120 @@ var init_protocol = __esm(() => {
106095
106124
  });
106096
106125
 
106097
106126
  // ../../shared/chat/pairs.js
106127
+ function accountGeneration(chatPK) {
106128
+ return accountGenerations.get(cleanText(chatPK)) || 0;
106129
+ }
106098
106130
  function getChatPairKey(chatPK, peerChatPK, chatId = "") {
106099
106131
  if (!chatPK || !peerChatPK)
106100
106132
  return null;
106101
106133
  return `${cleanText(chatPK)}|${cleanText(peerChatPK)}|${cleanText(chatId)}`;
106102
106134
  }
106103
- function clearChatPairCache() {
106104
- for (const pair of pairCache.values()) {
106105
- closeChatPair(pair);
106135
+ function closeEntry(entry) {
106136
+ if (!entry || entry.closed || entry.leases > 0) {
106137
+ return;
106138
+ }
106139
+ entry.closed = true;
106140
+ closeChatPair(entry.pair);
106141
+ }
106142
+ function retireEntry(entry) {
106143
+ if (!entry || entry.retired) {
106144
+ return;
106145
+ }
106146
+ entry.retired = true;
106147
+ closeEntry(entry);
106148
+ }
106149
+ function releaseEntry(entry) {
106150
+ if (!entry || entry.leases <= 0) {
106151
+ return;
106152
+ }
106153
+ entry.leases -= 1;
106154
+ if (entry.retired) {
106155
+ closeEntry(entry);
106156
+ }
106157
+ }
106158
+ function evictOverflow() {
106159
+ while (pairCache.size > MAX_PAIR_CACHE) {
106160
+ const firstKey = pairCache.keys().next().value;
106161
+ if (!firstKey) {
106162
+ return;
106163
+ }
106164
+ const entry = pairCache.get(firstKey);
106165
+ pairCache.delete(firstKey);
106166
+ retireEntry(entry);
106167
+ }
106168
+ }
106169
+ function clearChatPairCache(chatPK = "") {
106170
+ const account = cleanText(chatPK);
106171
+ if (!account) {
106172
+ cacheGeneration += 1;
106173
+ accountGenerations.clear();
106174
+ for (const entry of pairCache.values()) {
106175
+ retireEntry(entry);
106176
+ }
106177
+ pairCache.clear();
106178
+ return;
106179
+ }
106180
+ accountGenerations.set(account, accountGeneration(account) + 1);
106181
+ for (const [key, entry] of pairCache) {
106182
+ if (entry.account !== account) {
106183
+ continue;
106184
+ }
106185
+ pairCache.delete(key);
106186
+ retireEntry(entry);
106106
106187
  }
106107
- pairCache.clear();
106108
106188
  }
106109
- async function getCachedPair(chatPK, chatPrivKey, peerChatPK, options = {}) {
106189
+ async function acquireCachedPair(chatPK, chatPrivKey, peerChatPK, options = {}) {
106190
+ const account = cleanText(chatPK);
106110
106191
  const chatId = cleanText(options?.chatId);
106111
106192
  const key = getChatPairKey(chatPK, peerChatPK, chatId);
106193
+ const generation = cacheGeneration;
106194
+ const accountVersion = accountGeneration(account);
106112
106195
  if (!key) {
106113
- return openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
106196
+ const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
106197
+ const entry2 = { pair, leases: 1, retired: true, closed: false };
106198
+ return {
106199
+ pair,
106200
+ release: () => releaseEntry(entry2)
106201
+ };
106114
106202
  }
106115
- const cached = pairCache.get(key);
106116
- if (cached)
106117
- return cached;
106118
- const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
106119
- pairCache.set(key, pair);
106120
- if (pairCache.size > MAX_PAIR_CACHE) {
106121
- const firstKey = pairCache.keys().next().value;
106122
- if (firstKey) {
106123
- closeChatPair(pairCache.get(firstKey));
106124
- pairCache.delete(firstKey);
106203
+ let entry = pairCache.get(key);
106204
+ if (!entry) {
106205
+ const pair = await openChatPair(chatPK, chatPrivKey, peerChatPK, { chatId });
106206
+ if (generation !== cacheGeneration || accountVersion !== accountGeneration(account)) {
106207
+ const staleEntry = { pair, leases: 1, retired: true, closed: false };
106208
+ return {
106209
+ pair,
106210
+ release: () => releaseEntry(staleEntry)
106211
+ };
106125
106212
  }
106213
+ entry = pairCache.get(key);
106214
+ if (entry) {
106215
+ closeChatPair(pair);
106216
+ } else {
106217
+ entry = { account, pair, leases: 0, retired: false, closed: false };
106218
+ pairCache.set(key, entry);
106219
+ evictOverflow();
106220
+ }
106221
+ } else {
106222
+ pairCache.delete(key);
106223
+ pairCache.set(key, entry);
106224
+ }
106225
+ entry.leases += 1;
106226
+ return {
106227
+ pair: entry.pair,
106228
+ release: () => releaseEntry(entry)
106229
+ };
106230
+ }
106231
+ async function withCachedPair(chatPK, chatPrivKey, peerChatPK, options, runWithPair) {
106232
+ const { pair, release } = await acquireCachedPair(chatPK, chatPrivKey, peerChatPK, options);
106233
+ try {
106234
+ return await runWithPair(pair);
106235
+ } finally {
106236
+ release();
106126
106237
  }
106127
- return pair;
106128
106238
  }
106129
106239
  async function resolveLinkId(chatPK, chatPrivKey, peerChatPK) {
106130
- const pair = await getCachedPair(chatPK, chatPrivKey, peerChatPK);
106131
- return pair?.linkId || null;
106240
+ return withCachedPair(chatPK, chatPrivKey, peerChatPK, {}, (pair) => pair?.linkId || null);
106132
106241
  }
106133
106242
  async function resolvePeerChat(cloud, chatPK, chatPrivateKey, peerChatPK, options = {}) {
106134
106243
  const existingChatId = cleanText(options?.chatId);
@@ -106159,13 +106268,14 @@ async function resolvePeerChat(cloud, chatPK, chatPrivateKey, peerChatPK, option
106159
106268
  exists: linkChat?.exists === true
106160
106269
  };
106161
106270
  }
106162
- var pairCache, MAX_PAIR_CACHE;
106271
+ var pairCache, MAX_PAIR_CACHE, cacheGeneration = 0, accountGenerations;
106163
106272
  var init_pairs = __esm(() => {
106164
106273
  init_config();
106165
106274
  init_chat();
106166
106275
  init_protocol();
106167
106276
  pairCache = new Map;
106168
106277
  MAX_PAIR_CACHE = CHAT_PAIR_CACHE_LIMIT;
106278
+ accountGenerations = new Map;
106169
106279
  });
106170
106280
 
106171
106281
  // ../../shared/utils/diagnostics.js
@@ -106318,14 +106428,16 @@ function lockWallet(wallet) {
106318
106428
  cleanup?.catch?.(() => {});
106319
106429
  } catch {}
106320
106430
  }
106321
- function lockChat(chatPrivateKey) {
106431
+ function lockChat(chatPrivateKey, chatPK = "") {
106322
106432
  try {
106323
106433
  if (chatPrivateKey) {
106324
106434
  chatPrivateKey.fill(0);
106325
106435
  }
106326
106436
  } catch {}
106327
106437
  try {
106328
- clearChatPairCache();
106438
+ if (chatPK) {
106439
+ clearChatPairCache(chatPK);
106440
+ }
106329
106441
  } catch {}
106330
106442
  }
106331
106443
  var init_vault = __esm(() => {
@@ -106345,7 +106457,7 @@ function walletPubkey(value) {
106345
106457
  }
106346
106458
  return key;
106347
106459
  }
106348
- function closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner } = {}) {
106460
+ function closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner } = {}) {
106349
106461
  try {
106350
106462
  vaultAccess?.close?.();
106351
106463
  } catch {}
@@ -106356,7 +106468,7 @@ function closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess
106356
106468
  localCache?.close?.();
106357
106469
  } catch {}
106358
106470
  lockWallet(wallet);
106359
- lockChat(chatPrivateKey);
106471
+ lockChat(chatPrivateKey, chatPK);
106360
106472
  }
106361
106473
  function createAccountSession(resources = {}) {
106362
106474
  let closed = false;
@@ -106447,6 +106559,32 @@ function notifySeedDecrypted(callback, markDiag2, source) {
106447
106559
  failed(error);
106448
106560
  }
106449
106561
  }
106562
+ function startWalletBoot(bootWallet2, walletMnemonic, user, options) {
106563
+ let closed = false;
106564
+ let handedOff = false;
106565
+ let wallet = null;
106566
+ const result = Promise.resolve().then(() => bootWallet2(walletMnemonic, user, options)).then((identity) => {
106567
+ wallet = identity?.wallet || null;
106568
+ if (closed && wallet) {
106569
+ lockWallet(wallet);
106570
+ }
106571
+ return identity;
106572
+ });
106573
+ return {
106574
+ result,
106575
+ handoff() {
106576
+ handedOff = true;
106577
+ },
106578
+ close() {
106579
+ if (closed)
106580
+ return;
106581
+ closed = true;
106582
+ if (wallet && !handedOff) {
106583
+ lockWallet(wallet);
106584
+ }
106585
+ }
106586
+ };
106587
+ }
106450
106588
  async function openVaultAccountSession(vault, password, options = {}) {
106451
106589
  const {
106452
106590
  uid,
@@ -106488,6 +106626,7 @@ async function openVaultAccountSession(vault, password, options = {}) {
106488
106626
  throw new Error("unlockSettings missing");
106489
106627
  const { decryptSeed: decryptSeed2, unpackVaultSeedData } = vaultCrypto;
106490
106628
  let wallet = null;
106629
+ let chatPK = "";
106491
106630
  let chatPrivateKey = null;
106492
106631
  let localCache = null;
106493
106632
  let masterSeed = null;
@@ -106498,6 +106637,7 @@ async function openVaultAccountSession(vault, password, options = {}) {
106498
106637
  let vaultSigner = null;
106499
106638
  let vaultAccess = null;
106500
106639
  let vaultAccessResult = null;
106640
+ let walletBoot = null;
106501
106641
  let network;
106502
106642
  try {
106503
106643
  onStage?.("unlocking");
@@ -106566,46 +106706,18 @@ async function openVaultAccountSession(vault, password, options = {}) {
106566
106706
  onStage?.("wallet");
106567
106707
  const walletStartedAt = Date.now();
106568
106708
  mark(diag, "vault.unlock.wallet.start", { source });
106569
- const walletIdentity = await bootWallet2(walletMnemonic, user, { network, diag });
106570
- wallet = walletIdentity?.wallet;
106571
- const walletPK = walletPubkey(walletIdentity?.walletPK);
106572
- mark(diag, "vault.unlock.wallet.done", { elapsedMs: Date.now() - walletStartedAt, source });
106573
- requireCurrent(isCurrent);
106709
+ walletBoot = startWalletBoot(bootWallet2, walletMnemonic, user, { network, diag });
106574
106710
  onStage?.("chat");
106575
106711
  const chatStartedAt = Date.now();
106576
106712
  mark(diag, "vault.unlock.chat.start", { source });
106577
106713
  const chatIdentity = await bootChat2(chatSeed, user, { diag });
106578
106714
  chatPrivateKey = chatIdentity?.chatPrivateKey;
106579
- const chatPK = cleanText(chatIdentity?.chatPK);
106715
+ chatPK = cleanText(chatIdentity?.chatPK);
106580
106716
  if (!chatPrivateKey || !chatPK)
106581
106717
  throw new Error("chat identity missing");
106582
106718
  chatSeed = null;
106583
106719
  mark(diag, "vault.unlock.chat.done", { elapsedMs: Date.now() - chatStartedAt, source });
106584
106720
  requireCurrent(isCurrent);
106585
- const identityPayload = {};
106586
- if (walletIdentity?.walletPKNeedsWrite) {
106587
- identityPayload.walletPK = walletPK;
106588
- identityPayload.network = network;
106589
- }
106590
- if (chatIdentity?.chatPKNeedsWrite) {
106591
- identityPayload.chatPK = chatPK;
106592
- }
106593
- const identityFields = Object.keys(identityPayload);
106594
- let identityResult = null;
106595
- if (identityFields.length) {
106596
- const identitiesStartedAt = Date.now();
106597
- mark(diag, "vault.unlock.identities.start", { source, fields: identityFields });
106598
- identityResult = cloud.user.profile.identity.write(identityPayload).then(() => {
106599
- mark(diag, "vault.unlock.identities.done", {
106600
- elapsedMs: Date.now() - identitiesStartedAt,
106601
- source,
106602
- fields: identityFields
106603
- });
106604
- return { error: null };
106605
- }, (error) => ({ error }));
106606
- } else {
106607
- mark(diag, "vault.unlock.identities.skip", { source });
106608
- }
106609
106721
  const cacheStartedAt = Date.now();
106610
106722
  mark(diag, "vault.unlock.localCache.start", { source });
106611
106723
  localCache = await openLocalCache(cacheKey, { uid, network });
@@ -106613,20 +106725,16 @@ async function openVaultAccountSession(vault, password, options = {}) {
106613
106725
  cleanBytes(cacheKey);
106614
106726
  cacheKey = null;
106615
106727
  requireCurrent(isCurrent);
106616
- const identityWrite = await identityResult;
106617
- if (identityWrite?.error)
106618
- throw identityWrite.error;
106619
- requireCurrent(isCurrent);
106620
106728
  const signatureResult = await vaultAccessResult;
106621
106729
  if (signatureResult.error)
106622
106730
  throw signatureResult.error;
106623
106731
  vaultAccess = signatureResult.access;
106624
106732
  requireCurrent(isCurrent);
106625
- return createAccountSession({
106733
+ const session = createAccountSession({
106626
106734
  uid,
106627
106735
  network,
106628
- wallet,
106629
- walletPK,
106736
+ wallet: null,
106737
+ walletPK: null,
106630
106738
  chatPK,
106631
106739
  chatPrivateKey,
106632
106740
  localCache,
@@ -106634,12 +106742,56 @@ async function openVaultAccountSession(vault, password, options = {}) {
106634
106742
  vaultAccess,
106635
106743
  vaultPK: vaultSigner.publicKey
106636
106744
  });
106745
+ session.own(walletBoot);
106746
+ const walletReady = walletBoot.result.then(async (walletIdentity) => {
106747
+ if (session.closed)
106748
+ throw new Error("account session closed");
106749
+ const readyWallet = walletIdentity?.wallet;
106750
+ const walletPK = walletPubkey(walletIdentity?.walletPK);
106751
+ if (!readyWallet)
106752
+ throw new Error("wallet missing");
106753
+ mark(diag, "vault.unlock.wallet.done", { elapsedMs: Date.now() - walletStartedAt, source });
106754
+ const identityPayload = {};
106755
+ if (walletIdentity?.walletPKNeedsWrite) {
106756
+ identityPayload.walletPK = walletPK;
106757
+ identityPayload.network = network;
106758
+ }
106759
+ if (chatIdentity?.chatPKNeedsWrite) {
106760
+ identityPayload.chatPK = chatPK;
106761
+ }
106762
+ const identityFields = Object.keys(identityPayload);
106763
+ if (identityFields.length) {
106764
+ const identitiesStartedAt = Date.now();
106765
+ mark(diag, "vault.unlock.identities.start", { source, fields: identityFields });
106766
+ await cloud.user.profile.identity.write(identityPayload);
106767
+ mark(diag, "vault.unlock.identities.done", {
106768
+ elapsedMs: Date.now() - identitiesStartedAt,
106769
+ source,
106770
+ fields: identityFields
106771
+ });
106772
+ } else {
106773
+ mark(diag, "vault.unlock.identities.skip", { source });
106774
+ }
106775
+ if (session.closed)
106776
+ throw new Error("account session closed");
106777
+ walletBoot.handoff();
106778
+ session.wallet = readyWallet;
106779
+ session.walletPK = walletPK;
106780
+ return { wallet: readyWallet, walletPK };
106781
+ }).catch((error) => {
106782
+ walletBoot.close();
106783
+ throw error;
106784
+ });
106785
+ walletReady.catch(() => {});
106786
+ session.walletReady = walletReady;
106787
+ return session;
106637
106788
  } catch (error) {
106789
+ walletBoot?.close();
106638
106790
  if (vaultAccessResult && !vaultAccess) {
106639
106791
  const signatureResult = await vaultAccessResult;
106640
106792
  vaultAccess = signatureResult.access;
106641
106793
  }
106642
- closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner });
106794
+ closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner });
106643
106795
  throw error;
106644
106796
  } finally {
106645
106797
  cleanBytes(masterSeed, walletEntropy, chatSeed, cacheKey, settingsKey);
@@ -106654,6 +106806,7 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
106654
106806
  }
106655
106807
  const walletMnemonic = mnemonicFromWalletEntropy(walletEntropy);
106656
106808
  let wallet = null;
106809
+ let chatPK = "";
106657
106810
  let chatPrivateKey = null;
106658
106811
  let chatPubKey = null;
106659
106812
  let vaultSigner = null;
@@ -106682,10 +106835,11 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
106682
106835
  throw new Error("vault signature uid missing");
106683
106836
  vaultAccess = await cloud.auth.vaultSignature.open(vaultSigner);
106684
106837
  }
106838
+ chatPK = toHex(chatPubKey);
106685
106839
  return createAccountSession({
106686
106840
  wallet,
106687
106841
  walletPK,
106688
- chatPK: toHex(chatPubKey),
106842
+ chatPK,
106689
106843
  chatPrivateKey,
106690
106844
  localCache,
106691
106845
  network,
@@ -106694,7 +106848,7 @@ async function openAccountSessionFromSecrets(walletEntropy, chatSeed, masterSeed
106694
106848
  vaultPK: vaultSigner.publicKey
106695
106849
  });
106696
106850
  } catch (error) {
106697
- closeSessionResources({ wallet, chatPrivateKey, localCache, vaultAccess, vaultSigner });
106851
+ closeSessionResources({ wallet, chatPK, chatPrivateKey, localCache, vaultAccess, vaultSigner });
106698
106852
  throw error;
106699
106853
  } finally {
106700
106854
  cleanBytes(chatSeed, chatPubKey);
@@ -106866,18 +107020,13 @@ var init_values = __esm(() => {
106866
107020
 
106867
107021
  // ../../shared/avatar.js
106868
107022
  function readAvatarVersion(value) {
106869
- if (value == null || value === "" || typeof value !== "number" && typeof value !== "string") {
106870
- return null;
106871
- }
106872
- const version = Number(value);
106873
- return Number.isSafeInteger(version) && version >= 0 ? version : null;
107023
+ const version = typeof value === "string" ? value.trim().toLowerCase() : "";
107024
+ return AVATAR_VERSION_RE.test(version) ? version : null;
106874
107025
  }
106875
- function avatarUrlWithVersion(url, version) {
106876
- if (!url)
106877
- return null;
106878
- return version == null ? url : `${url}${url.includes("?") ? "&" : "?"}v=${encodeURIComponent(String(version))}`;
106879
- }
106880
- var init_avatar = () => {};
107026
+ var AVATAR_VERSION_RE;
107027
+ var init_avatar = __esm(() => {
107028
+ AVATAR_VERSION_RE = /^[0-9a-f]{64}$/;
107029
+ });
106881
107030
 
106882
107031
  // ../../shared/moderation.js
106883
107032
  function banUntilMs(ban) {
@@ -107155,11 +107304,16 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107155
107304
  markDiag(diag, "user.avatar.fetch.start", { force: !!force, persist: !!persist, hasVersion: avatarVersion != null });
107156
107305
  try {
107157
107306
  const promise = (async () => {
107158
- const nextAvatar = await cloud.peer.avatar.url(uid, { version: avatarVersion });
107159
- if (persist && avatarVersion != null) {
107160
- cloud.peer.avatar.read(uid).then((bytes) => writeCachedAvatar(uid, { version: avatarVersion, bytes })).catch(() => {});
107307
+ if (persist && avatarVersion != null && avatarCache && typeof cloud.peer.avatar.read === "function") {
107308
+ try {
107309
+ const bytes = await cloud.peer.avatar.read(uid, { version: avatarVersion });
107310
+ const cachedAvatar = await writeCachedAvatar(uid, { version: avatarVersion, bytes });
107311
+ if (cachedAvatar) {
107312
+ return cachedAvatar;
107313
+ }
107314
+ } catch {}
107161
107315
  }
107162
- return nextAvatar;
107316
+ return cloud.peer.avatar.url(uid, { version: avatarVersion });
107163
107317
  })().then((nextAvatar) => {
107164
107318
  markDone(diag, "user.avatar.fetch", startedAt, { found: !!nextAvatar, hasVersion: avatarVersion != null });
107165
107319
  if (!nextAvatar || avatarFetch.uid !== uid || avatarFetch.key !== key) {
@@ -107499,10 +107653,12 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107499
107653
  }
107500
107654
  function refetchAvatar(options = {}) {
107501
107655
  const requestedVersion = readAvatarVersion(options?.version);
107502
- const optimistic = options?.optimistic === true;
107503
- const nextVersion = requestedVersion ?? (optimistic && state.avatarVersion != null ? state.avatarVersion + 1 : state.avatarVersion);
107504
- const persist = options?.persist ?? !(optimistic && requestedVersion == null);
107505
- return fetchAvatar(state.uid, { force: true, persist, version: nextVersion });
107656
+ const nextVersion = requestedVersion ?? state.avatarVersion;
107657
+ return fetchAvatar(state.uid, {
107658
+ force: options?.force === true || state.avatarVersion !== nextVersion || !state.avatar,
107659
+ persist: options?.persist !== false,
107660
+ version: nextVersion
107661
+ });
107506
107662
  }
107507
107663
  function setNetwork(nextNetwork) {
107508
107664
  if (activeNetwork === nextNetwork)
@@ -107770,7 +107926,7 @@ function getSubtleCrypto() {
107770
107926
  async function importAesKey(key, usages) {
107771
107927
  return getSubtleCrypto().importKey("raw", toBytes32(key, "file key"), { name: "AES-GCM" }, false, usages);
107772
107928
  }
107773
- async function sealFile(_pair, key, bytes, scope) {
107929
+ async function sealFile(key, bytes, scope) {
107774
107930
  const fileKey = new Uint8Array(toBytes32(key, "file key"));
107775
107931
  try {
107776
107932
  const cryptoKey = await importAesKey(fileKey, ["encrypt"]);
@@ -107933,20 +108089,22 @@ async function toUploadBytes(data) {
107933
108089
  }
107934
108090
  return toBytes2(data, "upload bytes");
107935
108091
  }
107936
- async function makeChatFileUploadPayload(pair, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
107937
- const chatId = cleanMediaChatId(pair?.chatId);
108092
+ async function makeChatFileUploadPayload(chatId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
108093
+ const nextChatId = cleanMediaChatId(chatId);
107938
108094
  const mediaId = makeChatMediaId();
107939
- const path = mediaFilePath(chatId, mediaId);
108095
+ const path = mediaFilePath(nextChatId, mediaId);
107940
108096
  const expiresAt = Date.now() + CHAT_MEDIA_TTL_MS2;
107941
108097
  const key = createFileKey();
107942
108098
  try {
107943
108099
  const uploadBytes = await toUploadBytes(data);
107944
108100
  assertChatUploadByteSize(uploadBytes);
108101
+ const body = await sealFile(key, uploadBytes, path);
108102
+ assertChatUploadByteSize(body);
107945
108103
  return {
107946
- chatId,
108104
+ chatId: nextChatId,
107947
108105
  mediaId,
107948
108106
  path,
107949
- body: await sealFile(pair, key, uploadBytes, path),
108107
+ body,
107950
108108
  metadata: {
107951
108109
  contentType: "application/octet-stream",
107952
108110
  cacheControl
@@ -107975,10 +108133,12 @@ async function makeSharedFileUploadPayload(data, { contentType = "application/oc
107975
108133
  try {
107976
108134
  const uploadBytes = await toUploadBytes(data);
107977
108135
  assertChatUploadByteSize(uploadBytes);
108136
+ const body = await sealFile(key, uploadBytes, path);
108137
+ assertChatUploadByteSize(body);
107978
108138
  return {
107979
108139
  sharedId,
107980
108140
  path,
107981
- body: await sealFile(null, key, uploadBytes, path),
108141
+ body,
107982
108142
  metadata: {
107983
108143
  contentType,
107984
108144
  cacheControl
@@ -109904,10 +110064,19 @@ function sortedUniqueValues(items) {
109904
110064
  }
109905
110065
 
109906
110066
  // ../../shared/chat/chats.js
110067
+ function sameSigningKeys(left, right) {
110068
+ if (left === right)
110069
+ return true;
110070
+ const leftKeys = Object.keys(left || {});
110071
+ const rightKeys = Object.keys(right || {});
110072
+ if (leftKeys.length !== rightKeys.length)
110073
+ return false;
110074
+ return leftKeys.every((key) => left[key] === right[key]);
110075
+ }
109907
110076
  function sameChatShape(a, b) {
109908
110077
  if (!a || !b)
109909
110078
  return a === b;
109910
- 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);
110079
+ return a.id === b.id && a.protocol === b.protocol && a.linkId === b.linkId && a.entryId === b.entryId && a.peerChatPK === b.peerChatPK && a.peerUid === b.peerUid && sameSigningKeys(a.signingKeysByChatKey, b.signingKeysByChatKey) && a.signerShared === b.signerShared && a.ts === b.ts && a.startMs === b.startMs && a.unseen === b.unseen && a.settings?.retention === b.settings?.retention && sameChatPreview(a.preview, b.preview);
109911
110080
  }
109912
110081
  function sameChats(prev, next) {
109913
110082
  if (prev.length !== next.length)
@@ -110266,8 +110435,10 @@ function emptyPayload() {
110266
110435
  chatsById: {},
110267
110436
  profilesByUid: {},
110268
110437
  mediaByKey: {},
110438
+ walletBalance: null,
110269
110439
  resumeRoute: null,
110270
- lastCameraFacing: null
110440
+ lastCameraFacing: null,
110441
+ cloaked: false
110271
110442
  };
110272
110443
  }
110273
110444
  function isObject3(value) {
@@ -110328,8 +110499,26 @@ function normalizePayload(value) {
110328
110499
  chatsById: isObject3(input.chatsById) ? input.chatsById : {},
110329
110500
  profilesByUid: isObject3(input.profilesByUid) ? input.profilesByUid : {},
110330
110501
  mediaByKey: isObject3(input.mediaByKey) ? input.mediaByKey : {},
110502
+ walletBalance: cleanWalletBalance(input.walletBalance),
110331
110503
  resumeRoute: cleanResumeRoute(input.resumeRoute),
110332
- lastCameraFacing: cleanCameraFacing(input.lastCameraFacing)
110504
+ lastCameraFacing: cleanCameraFacing(input.lastCameraFacing),
110505
+ cloaked: input.cloaked === true
110506
+ };
110507
+ }
110508
+ function cleanWalletBalance(value) {
110509
+ if (!isObject3(value))
110510
+ return null;
110511
+ const walletPK = cleanText(value.walletPK).toLowerCase();
110512
+ const network = cleanText(value.network).toLowerCase();
110513
+ const balance = Number(value.balance);
110514
+ if (!walletPK || !network || !Number.isSafeInteger(balance) || balance < 0) {
110515
+ return null;
110516
+ }
110517
+ return {
110518
+ walletPK,
110519
+ network,
110520
+ balance,
110521
+ savedAt: Number.isFinite(value.savedAt) ? value.savedAt : 0
110333
110522
  };
110334
110523
  }
110335
110524
  function cleanResumeRoute(route) {
@@ -110775,8 +110964,8 @@ function stateFromPayload(payload, { chatId, selfChatPublicKey, peerChatPublicKe
110775
110964
  if (!cleanOlder.length && !cleanLive.length) {
110776
110965
  return { rejectReason: "empty-message-cache" };
110777
110966
  }
110778
- const historyStartReached = payload.historyStartReached === true && payload.truncated !== true;
110779
- const payloadHasOlder = historyStartReached ? false : payload.hasOlder === true || payload.truncated === true;
110967
+ const historyStartReached = payload.historyStartReached === true;
110968
+ const payloadHasOlder = payload.hasOlder === true || payload.truncated === true;
110780
110969
  const partialOlderThan = partial ? markerFromMessage(cleanOlder[0]) : null;
110781
110970
  return {
110782
110971
  historyMessages: cleanOlder,
@@ -110921,7 +111110,7 @@ function writeCachedMessageState(cache, { stateVersion = null, chatId, selfChatP
110921
111110
  signingKeysVersion: cleanText(signingKeysVersion),
110922
111111
  savedAt: Date.now(),
110923
111112
  hasOlder: nextHasOlder === true || compacted.truncated,
110924
- historyStartReached: nextHistoryStartReached === true && compacted.truncated !== true,
111113
+ historyStartReached: nextHistoryStartReached === true,
110925
111114
  olderThan: serializeMarker(storedOlderThan),
110926
111115
  olderLoaded: nextOlderLoaded === true || compacted.historyMessages.length > 0,
110927
111116
  latestServerPage: batchFromLive(compacted.latestMessages, expiredKeys, deletedKeys, nextServerBatch),
@@ -111180,6 +111369,36 @@ var init_chats2 = __esm(() => {
111180
111369
  "use client";
111181
111370
  });
111182
111371
 
111372
+ // ../../shared/cache/localdata/balance.js
111373
+ function normalizedBalance(value) {
111374
+ const balance = Number(value);
111375
+ return Number.isSafeInteger(balance) && balance >= 0 ? balance : null;
111376
+ }
111377
+ function writeCachedWalletBalance(cache, { walletPK = null, network = null, balance = null } = {}) {
111378
+ const nextWalletPK = lowerText(walletPK);
111379
+ const nextNetwork = lowerText(network);
111380
+ const nextBalance = normalizedBalance(balance);
111381
+ if (!cache || !nextWalletPK || !nextNetwork || nextBalance == null) {
111382
+ return Promise.resolve(false);
111383
+ }
111384
+ const current = cache.read?.()?.walletBalance;
111385
+ if (sameText(current?.walletPK, nextWalletPK) && sameText(current?.network, nextNetwork) && normalizedBalance(current?.balance) === nextBalance) {
111386
+ return Promise.resolve(false);
111387
+ }
111388
+ return cache.patch((draft) => {
111389
+ draft.walletBalance = {
111390
+ walletPK: nextWalletPK,
111391
+ network: nextNetwork,
111392
+ balance: nextBalance,
111393
+ savedAt: Date.now()
111394
+ };
111395
+ return draft;
111396
+ }).then(() => true);
111397
+ }
111398
+ var init_balance = __esm(() => {
111399
+ "use client";
111400
+ });
111401
+
111183
111402
  // ../../shared/cache/localdata/profiles.js
111184
111403
  function isUsableProfile(profile, now = Date.now()) {
111185
111404
  if (!profile?.uid || !profile.walletPK && !profile.chatPK) {
@@ -111420,24 +111639,26 @@ async function readCachedTransferHeadState(cache, { walletPK = null } = {}) {
111420
111639
  const stored = cache ? await cache.readPart(TX_HEAD_PART).catch(() => null) : null;
111421
111640
  return stateFromPayload2(stored, { walletPK, source: "head" });
111422
111641
  }
111423
- function writeCachedTransferState(cache, { transfers, walletPK = null, serverEndReached = false, nextOffset = null, oldestTxMs = null } = {}) {
111424
- if (!cache || !Array.isArray(transfers)) {
111425
- return Promise.resolve(false);
111642
+ function cachedTransferPayloads({ transfers, walletPK = null, serverEndReached = false, historyCount = null, nextOffset = null, oldestTxMs = null } = {}) {
111643
+ if (!Array.isArray(transfers)) {
111644
+ return null;
111426
111645
  }
111427
111646
  const nextWalletPK = lowerText(walletPK);
111428
111647
  if (!nextWalletPK) {
111429
- return Promise.resolve(false);
111648
+ return null;
111430
111649
  }
111431
111650
  const visible = [...transfers].filter((tx) => tx?.id && isVisibleTransfer(tx) && transferBelongsToWallet(tx, nextWalletPK)).sort((a, b) => txCreatedMs(b) - txCreatedMs(a));
111432
111651
  const sorted2 = visible.slice(0, Math.max(1, WALLET_TRANSFER_CACHE_LIMIT)).map(jsonClean);
111433
111652
  const capped = sorted2.length < visible.length;
111653
+ const knownHistoryCount = Math.max(sorted2.length, Math.floor(Number(historyCount) || 0));
111654
+ const complete = serverEndReached === true && !capped && sorted2.length >= knownHistoryCount;
111434
111655
  const fullPayload = {
111435
111656
  version: CACHE_VERSION,
111436
111657
  walletPK: nextWalletPK,
111437
- serverEndReached: serverEndReached === true && !capped,
111438
- historyCount: sorted2.length,
111658
+ serverEndReached: complete,
111659
+ historyCount: knownHistoryCount,
111439
111660
  nextOffset: Number.isFinite(nextOffset) ? nextOffset : sorted2.length,
111440
- oldestTxMs: serverEndReached === true && !capped && Number.isFinite(oldestTxMs) ? oldestTxMs : null,
111661
+ oldestTxMs: complete && Number.isFinite(oldestTxMs) ? oldestTxMs : null,
111441
111662
  savedAt: Date.now(),
111442
111663
  transfers: sorted2
111443
111664
  };
@@ -111446,7 +111667,24 @@ function writeCachedTransferState(cache, { transfers, walletPK = null, serverEnd
111446
111667
  ...fullPayload,
111447
111668
  transfers: headTransfers
111448
111669
  };
111449
- return Promise.all([cache.writePart(TX_HEAD_PART, headPayload), cache.writePart(TX_PART, fullPayload)]).then((results) => results.some(Boolean));
111670
+ return { fullPayload, headPayload };
111671
+ }
111672
+ function writeCachedTransferHeadState(cache, state = {}) {
111673
+ if (!cache) {
111674
+ return Promise.resolve(false);
111675
+ }
111676
+ const payloads = cachedTransferPayloads(state);
111677
+ return payloads ? cache.writePart(TX_HEAD_PART, payloads.headPayload) : Promise.resolve(false);
111678
+ }
111679
+ function writeCachedTransferState(cache, state = {}) {
111680
+ if (!cache) {
111681
+ return Promise.resolve(false);
111682
+ }
111683
+ const payloads = cachedTransferPayloads(state);
111684
+ if (!payloads) {
111685
+ return Promise.resolve(false);
111686
+ }
111687
+ return Promise.all([cache.writePart(TX_HEAD_PART, payloads.headPayload), cache.writePart(TX_PART, payloads.fullPayload)]).then((results) => results.some(Boolean));
111450
111688
  }
111451
111689
  var TX_PART = "wallet-transfers", TX_HEAD_PART = "wallet-transfers-head", CACHE_VERSION = 2, CACHE_HEAD_LIMIT;
111452
111690
  var init_transfers = __esm(() => {
@@ -111470,8 +111708,14 @@ function mediaAad(uid, network, id) {
111470
111708
  function partAad(uid, network, part) {
111471
111709
  return encoder.encode(JSON.stringify(["veyl-local-cache-part", CACHE_PART_ENVELOPE_VERSION, String(uid || ""), String(network || ""), String(part || "")]));
111472
111710
  }
111711
+ function encodePayloadData(payload) {
111712
+ return encoder.encode(JSON.stringify({ ...normalizePayload(payload), savedAt: Date.now() }));
111713
+ }
111714
+ function decodePayloadData(raw) {
111715
+ return JSON.parse(decoder.decode(toBytes2(raw, "cache payload")));
111716
+ }
111473
111717
  async function sealPayload(key, payload, uid, network) {
111474
- const body = encoder.encode(JSON.stringify({ ...normalizePayload(payload), savedAt: Date.now() }));
111718
+ const body = encodePayloadData(payload);
111475
111719
  try {
111476
111720
  const { iv, ct } = await sealAes(key, body, cacheAad(uid, network));
111477
111721
  return JSON.stringify({
@@ -111493,7 +111737,7 @@ async function openPayloadData(key, raw, uid, network) {
111493
111737
  }
111494
111738
  const plain = await openAes(key, fromHexBytes(envelope.iv, "cache iv"), fromHexBytes(envelope.ct, "cache ciphertext"), cacheAad(uid, network));
111495
111739
  try {
111496
- return JSON.parse(decoder.decode(plain));
111740
+ return decodePayloadData(plain);
111497
111741
  } finally {
111498
111742
  cleanBytes(plain);
111499
111743
  }
@@ -111554,6 +111798,14 @@ function mediaCrypto(uid, network, id, keyBytes) {
111554
111798
  ivBytes: AES_IV_BYTES
111555
111799
  };
111556
111800
  }
111801
+ function payloadCrypto(uid, network, keyBytes) {
111802
+ return {
111803
+ key: keyBytes,
111804
+ aad: cacheAad(uid, network),
111805
+ version: LOCAL_DATA_CACHE_VERSION,
111806
+ ivBytes: AES_IV_BYTES
111807
+ };
111808
+ }
111557
111809
  var MEDIA_ENVELOPE_VERSION = 1, CACHE_PART_ENVELOPE_VERSION = 1;
111558
111810
  var init_crypto = __esm(() => {
111559
111811
  init_aes2();
@@ -111589,11 +111841,32 @@ async function openVaultCache({ key, storage, uid, network }) {
111589
111841
  let writeTimer = null;
111590
111842
  let writeTask = Promise.resolve();
111591
111843
  const partWriteTasks = new Map;
111844
+ let storedPayload = null;
111592
111845
  try {
111593
- const rawPayload = await openPayloadData(cacheKey, await storage.read(), uid, network);
111594
- payload = normalizePayload(rawPayload);
111846
+ storedPayload = await storage.read();
111847
+ if (!storedPayload) {
111848
+ payload = emptyPayload();
111849
+ } else if (typeof storage.openPayload === "function") {
111850
+ const readKey = new Uint8Array(cacheKey);
111851
+ let plain;
111852
+ try {
111853
+ plain = await storage.openPayload(storedPayload, payloadCrypto(uid, network, readKey));
111854
+ payload = normalizePayload(decodePayloadData(plain));
111855
+ } finally {
111856
+ cleanBytes(plain, readKey);
111857
+ }
111858
+ } else {
111859
+ payload = normalizePayload(await openPayloadData(cacheKey, storedPayload, uid, network));
111860
+ }
111595
111861
  } catch {
111596
111862
  payload = emptyPayload();
111863
+ if (storedPayload) {
111864
+ await Promise.all([
111865
+ storage.remove().catch(() => {}),
111866
+ storage.removeAllMedia().catch(() => {}),
111867
+ storage.removeAllParts().catch(() => {})
111868
+ ]);
111869
+ }
111597
111870
  }
111598
111871
  const writeNow = () => {
111599
111872
  if (closed) {
@@ -111611,10 +111884,12 @@ async function openVaultCache({ key, storage, uid, network }) {
111611
111884
  return;
111612
111885
  }
111613
111886
  const writeKey = new Uint8Array(cacheKey);
111887
+ let body;
111614
111888
  try {
111615
- await storage.write(await sealPayload(writeKey, payload, uid, network));
111889
+ const raw = typeof storage.sealPayload === "function" ? await storage.sealPayload(body = encodePayloadData(payload), payloadCrypto(uid, network, writeKey)) : await sealPayload(writeKey, payload, uid, network);
111890
+ await storage.write(raw);
111616
111891
  } finally {
111617
- cleanBytes(writeKey);
111892
+ cleanBytes(body, writeKey);
111618
111893
  }
111619
111894
  }).catch(() => {});
111620
111895
  return writeTask;
@@ -111885,6 +112160,7 @@ var init_vault2 = __esm(() => {
111885
112160
  // ../../shared/cache/localdata.js
111886
112161
  var init_localdata = __esm(() => {
111887
112162
  init_chats2();
112163
+ init_balance();
111888
112164
  init_media();
111889
112165
  init_messages2();
111890
112166
  init_profiles();
@@ -111962,7 +112238,8 @@ function makeOwnChatEntry(pair, fields = {}) {
111962
112238
  preview: fields.preview || null,
111963
112239
  saved: fields.saved || null,
111964
112240
  readMs: Number.isFinite(fields.readMs) ? fields.readMs : null,
111965
- startMs: Number.isFinite(fields.startMs) ? fields.startMs : null
112241
+ startMs: Number.isFinite(fields.startMs) ? fields.startMs : null,
112242
+ signerShared: fields.signerShared === true
111966
112243
  };
111967
112244
  }
111968
112245
  var CHAT_ENTRY_VERSION = 1;
@@ -112417,9 +112694,9 @@ function setErrorStage(error, stage, extra = {}) {
112417
112694
  Object.assign(error, extra);
112418
112695
  return error;
112419
112696
  }
112420
- async function makeChatFileUpload(pair, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
112697
+ async function makeChatFileUpload(chatId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
112421
112698
  try {
112422
- return await makeChatFileUploadPayload(pair, cid, data, {
112699
+ return await makeChatFileUploadPayload(chatId, cid, data, {
112423
112700
  cacheControl
112424
112701
  });
112425
112702
  } catch (error) {
@@ -112429,8 +112706,8 @@ async function makeChatFileUpload(pair, cid, data, { cacheControl = "private, ma
112429
112706
  });
112430
112707
  }
112431
112708
  }
112432
- async function putChatFile(pair, cid, data, options) {
112433
- const upload = await makeChatFileUpload(pair, cid, data, options);
112709
+ async function putChatFile(chatId, cid, data, options) {
112710
+ const upload = await makeChatFileUpload(chatId, cid, data, options);
112434
112711
  try {
112435
112712
  if (typeof options?.uploadChatMedia !== "function") {
112436
112713
  throw new Error("chat media upload required");
@@ -112465,7 +112742,7 @@ async function putSharedFile(data, options = {}) {
112465
112742
  throw setErrorStage(error, "upload", { path: upload.path });
112466
112743
  }
112467
112744
  }
112468
- async function readChatFile(readChatMedia, _pair, file) {
112745
+ async function readChatFile(readChatMedia, file) {
112469
112746
  let body;
112470
112747
  try {
112471
112748
  if (typeof readChatMedia !== "function") {
@@ -112523,8 +112800,8 @@ function buildAttachmentMsg(type, file) {
112523
112800
  return makeAttachment(type, file);
112524
112801
  }
112525
112802
  }
112526
- async function putAttachment(pair, cid, type, data, meta = {}) {
112527
- const file = await putChatFile(pair, cid, data, meta);
112803
+ async function putAttachment(chatId, cid, type, data, meta = {}) {
112804
+ const file = await putChatFile(chatId, cid, data, meta);
112528
112805
  return buildAttachmentMsg(type, {
112529
112806
  ...file,
112530
112807
  ...pickAttachmentMeta(meta)
@@ -112537,26 +112814,26 @@ async function putSharedAttachment(type, data, meta = {}) {
112537
112814
  ...pickAttachmentMeta(meta)
112538
112815
  });
112539
112816
  }
112540
- async function putImg(pair, cid, data, meta = {}) {
112541
- return putAttachment(pair, cid, "img", data, meta);
112817
+ async function putImg(chatId, cid, data, meta = {}) {
112818
+ return putAttachment(chatId, cid, "img", data, meta);
112542
112819
  }
112543
- async function putGif(pair, cid, data, meta = {}) {
112544
- return putAttachment(pair, cid, "gif", data, meta);
112820
+ async function putGif(chatId, cid, data, meta = {}) {
112821
+ return putAttachment(chatId, cid, "gif", data, meta);
112545
112822
  }
112546
- async function putM4a(pair, cid, data, meta = {}) {
112547
- return putAttachment(pair, cid, "m4a", data, meta);
112823
+ async function putM4a(chatId, cid, data, meta = {}) {
112824
+ return putAttachment(chatId, cid, "m4a", data, meta);
112548
112825
  }
112549
- async function putMp4(pair, cid, data, meta = {}) {
112550
- return putAttachment(pair, cid, "mp4", data, meta);
112826
+ async function putMp4(chatId, cid, data, meta = {}) {
112827
+ return putAttachment(chatId, cid, "mp4", data, meta);
112551
112828
  }
112552
- async function putFile(pair, cid, data, meta = {}) {
112553
- return putAttachment(pair, cid, "file", data, meta);
112829
+ async function putFile(chatId, cid, data, meta = {}) {
112830
+ return putAttachment(chatId, cid, "file", data, meta);
112554
112831
  }
112555
- function readMsgAttachment(readChatMedia, pair, msg) {
112556
- return readChatFile(readChatMedia, pair, msg);
112832
+ function readMsgAttachment(readChatMedia, msg) {
112833
+ return readChatFile(readChatMedia, msg);
112557
112834
  }
112558
- function readMsgFile(readChatMedia, pair, msg) {
112559
- return readMsgAttachment(readChatMedia, pair, msg);
112835
+ function readMsgFile(readChatMedia, msg) {
112836
+ return readMsgAttachment(readChatMedia, msg);
112560
112837
  }
112561
112838
  var init_media2 = __esm(() => {
112562
112839
  init_files2();
@@ -112727,7 +113004,25 @@ async function setChatRead(cloud, uid, chatPrivKey, chatId, readMs, options = {}
112727
113004
  body: await sealOwnChatEntry(chatPrivKey, entryId, {
112728
113005
  ...entry,
112729
113006
  readMs: Math.max(currentReadMs, nextReadMs),
112730
- ...hasPreviewPatch ? { preview: preview2 || null } : {}
113007
+ ...hasPreviewPatch ? { preview: preview2 || null } : {},
113008
+ ...options.signerShared === true ? { signerShared: true } : {}
113009
+ })
113010
+ });
113011
+ return true;
113012
+ }
113013
+ async function setChatSignerShared(cloud, uid, chatPrivKey, chatId) {
113014
+ if (!cloud || !uid || !chatPrivKey || !chatId) {
113015
+ return false;
113016
+ }
113017
+ const entryId = ownChatEntryId(chatPrivKey, chatId);
113018
+ const entry = await readOwnEntry(cloud, uid, chatPrivKey, entryId);
113019
+ if (!entry?.chatId || entry.signerShared === true) {
113020
+ return entry?.signerShared === true;
113021
+ }
113022
+ await cloud.user.chats.write(uid, entryId, {
113023
+ body: await sealOwnChatEntry(chatPrivKey, entryId, {
113024
+ ...entry,
113025
+ signerShared: true
112731
113026
  })
112732
113027
  });
112733
113028
  return true;
@@ -112770,7 +113065,8 @@ async function ownEntryWrite(cloud, uid, chatPrivKey, pair, fields = {}) {
112770
113065
  preview: fields.preview || existing?.preview,
112771
113066
  saved: existing?.saved || null,
112772
113067
  readMs: existing?.readMs,
112773
- startMs: existing?.startMs
113068
+ startMs: existing?.startMs,
113069
+ signerShared: fields.signerShared === true || existing?.signerShared === true
112774
113070
  });
112775
113071
  const tsMs = timestampMs(fields.ts, null);
112776
113072
  return {
@@ -112805,14 +113101,6 @@ function ownerEditedPreview(senderPubkey, message) {
112805
113101
  failed: false
112806
113102
  };
112807
113103
  }
112808
- function messageFileChatId(message) {
112809
- try {
112810
- const ref = getMediaFileRef(message?.p);
112811
- return ref?.type === "chat" ? ref.chatId : "";
112812
- } catch {
112813
- return "";
112814
- }
112815
- }
112816
113104
  async function recipientForSend(cloud, receiverChatPK, options, needed) {
112817
113105
  if (!needed) {
112818
113106
  return null;
@@ -112833,50 +113121,63 @@ async function sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, messa
112833
113121
  if (!chatId) {
112834
113122
  throw new Error("chat id required");
112835
113123
  }
112836
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
112837
- if (options?.linkId && cleanText(options.linkId) !== pair.linkId) {
112838
- throw new Error("link mismatch");
112839
- }
112840
- const retention = cleanChatRetention(options?.retention ?? options?.ttlMode);
112841
- const tsMs = Date.now();
112842
- const messagePayload = withMessageRetention(message, retention);
112843
- const actionOp = actionOpForPayload(messagePayload);
112844
- const { head, body } = await sealMsg(pair, messagePayload, { op: actionOp, ts: tsMs });
112845
- const ttlMs = actionOp === CHAT_ACTION_OPS.CREATE ? newStorageTtlMs() : null;
112846
- const messageId = head.cid;
112847
- const msgData = {
112848
- head,
112849
- body,
112850
- ttlMs
112851
- };
112852
- const recipientProfile = await recipientForSend(cloud, receiverChatPK, options, updatePreview || options?.ping === true);
112853
- const sentMessage = ownerPreview(senderPubkey, messagePayload, messageId, head, tsMs, ttlMs);
112854
- const preview2 = updatePreview ? sentMessage : null;
112855
- const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
112856
- peerUid: recipientProfile?.uid || cleanText(options?.receiverUid),
112857
- peerSigningPublicKey: recipientProfile?.signingPublicKey,
112858
- entry: options?.ownEntry,
112859
- settings: options?.chatSettings,
112860
- preview: preview2,
112861
- ts: tsMs
112862
- }) : null;
112863
- const pingKind = updatePreview ? "message" : cleanText(options?.pingKind) || "ping";
112864
- const ping = recipientProfile?.uid && (updatePreview || options?.ping === true) ? await sealPing(senderPubkey, senderPrivkey, receiverChatPK, {
112865
- kind: pingKind,
112866
- chatId,
112867
- senderUid: cleanText(options?.senderUid),
112868
- messageId,
112869
- ts: tsMs
112870
- }) : null;
112871
- const write = await cloud.chat.messages.send({
112872
- chatId,
112873
- messageId,
112874
- message: msgData,
112875
- ownerEntry,
112876
- inbox: ping ? { recipientUid: recipientProfile?.uid, ping } : null,
112877
- onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
113124
+ return withCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId }, async (pair) => {
113125
+ if (options?.linkId && cleanText(options.linkId) !== pair.linkId) {
113126
+ throw new Error("link mismatch");
113127
+ }
113128
+ const retention = cleanChatRetention(options?.retention ?? options?.ttlMode);
113129
+ const tsMs = Date.now();
113130
+ const messagePayload = withMessageRetention(message, retention);
113131
+ const actionOp = actionOpForPayload(messagePayload);
113132
+ const { head, body } = await sealMsg(pair, messagePayload, { op: actionOp, ts: tsMs });
113133
+ const ttlMs = actionOp === CHAT_ACTION_OPS.CREATE ? newStorageTtlMs() : null;
113134
+ const messageId = head.cid;
113135
+ const msgData = {
113136
+ head,
113137
+ body,
113138
+ ttlMs
113139
+ };
113140
+ const recipientProfile = await recipientForSend(cloud, receiverChatPK, options, updatePreview || options?.ping === true);
113141
+ const sentMessage = ownerPreview(senderPubkey, messagePayload, messageId, head, tsMs, ttlMs);
113142
+ const preview2 = updatePreview ? sentMessage : null;
113143
+ const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
113144
+ peerUid: recipientProfile?.uid || cleanText(options?.receiverUid),
113145
+ peerSigningPublicKey: recipientProfile?.signingPublicKey,
113146
+ entry: options?.ownEntry,
113147
+ settings: options?.chatSettings,
113148
+ preview: preview2,
113149
+ ts: tsMs
113150
+ }) : null;
113151
+ const pingKind = updatePreview ? "message" : cleanText(options?.pingKind) || "ping";
113152
+ const ping = recipientProfile?.uid && (updatePreview || options?.ping === true) ? await sealPing(senderPubkey, senderPrivkey, receiverChatPK, {
113153
+ kind: pingKind,
113154
+ chatId,
113155
+ senderUid: cleanText(options?.senderUid),
113156
+ messageId,
113157
+ ts: tsMs
113158
+ }) : null;
113159
+ const write = await cloud.chat.messages.send({
113160
+ chatId,
113161
+ messageId,
113162
+ message: msgData,
113163
+ ownerEntry,
113164
+ inbox: ping ? { recipientUid: recipientProfile?.uid, ping, notify: options?.notify !== false } : null,
113165
+ onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
113166
+ });
113167
+ const delivered = options?.ping === true ? !!ping && write?.delivered !== false : write?.delivered !== false;
113168
+ if (ping && delivered && ownerEntry && options?.ownEntry?.signerShared !== true) {
113169
+ await setChatSignerShared(cloud, cleanText(options?.senderUid), senderPrivkey, chatId).catch(() => false);
113170
+ }
113171
+ return {
113172
+ chatId,
113173
+ msgId: messageId,
113174
+ cid: head.cid,
113175
+ message: sentMessage,
113176
+ preview: preview2,
113177
+ delivered,
113178
+ ...write?.skipped ? { skipped: true } : {}
113179
+ };
112878
113180
  });
112879
- return { chatId, msgId: messageId, cid: head.cid, message: sentMessage, preview: preview2, ...write?.skipped ? { skipped: true } : {} };
112880
113181
  }
112881
113182
  async function sendReadReceipt(cloud, senderPubkey, senderPrivkey, receiverChatPK, target, options = {}) {
112882
113183
  const receipt = {
@@ -112884,7 +113185,13 @@ async function sendReadReceipt(cloud, senderPubkey, senderPrivkey, receiverChatP
112884
113185
  cid: makeCid(),
112885
113186
  s: senderPubkey
112886
113187
  };
112887
- return sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, receipt, { ...options, updatePreview: false, ping: false });
113188
+ return sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, receipt, {
113189
+ ...options,
113190
+ updatePreview: false,
113191
+ ping: options?.ping === true,
113192
+ pingKind: "read",
113193
+ notify: false
113194
+ });
112888
113195
  }
112889
113196
  async function sendReaction(cloud, senderPubkey, senderPrivkey, receiverChatPK, target, emoji, options = {}) {
112890
113197
  const reaction = {
@@ -112986,27 +113293,28 @@ async function setChatRetention(cloud, chatId, senderPubkey, senderPrivkey, peer
112986
113293
  throw new Error("vault locked");
112987
113294
  }
112988
113295
  const nextRetention = cleanChatRetention(retention);
112989
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
112990
- if (chatId && pair.chatId !== chatId) {
112991
- throw new Error("chat mismatch");
112992
- }
112993
- const systemMessage = {
112994
- ...makeRetentionSystemMsg(nextRetention),
112995
- cid: cleanText(options?.cid) || makeCid(),
112996
- s: senderPubkey
112997
- };
112998
- await sendMsg(cloud, senderPubkey, senderPrivkey, peerChatPK, systemMessage, {
112999
- chatId,
113000
- linkId: pair.linkId,
113001
- updatePreview: true,
113002
- retention: nextRetention,
113003
- chatExists: true,
113004
- senderUid: options?.senderUid,
113005
- ownEntry: options?.ownEntry,
113006
- chatSettings: { retention: nextRetention },
113007
- onCommitted: options?.onCommitted
113296
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
113297
+ if (chatId && pair.chatId !== chatId) {
113298
+ throw new Error("chat mismatch");
113299
+ }
113300
+ const systemMessage = {
113301
+ ...makeRetentionSystemMsg(nextRetention),
113302
+ cid: cleanText(options?.cid) || makeCid(),
113303
+ s: senderPubkey
113304
+ };
113305
+ await sendMsg(cloud, senderPubkey, senderPrivkey, peerChatPK, systemMessage, {
113306
+ chatId,
113307
+ linkId: pair.linkId,
113308
+ updatePreview: true,
113309
+ retention: nextRetention,
113310
+ chatExists: true,
113311
+ senderUid: options?.senderUid,
113312
+ ownEntry: options?.ownEntry,
113313
+ chatSettings: { retention: nextRetention },
113314
+ onCommitted: options?.onCommitted
113315
+ });
113316
+ return nextRetention;
113008
113317
  });
113009
- return nextRetention;
113010
113318
  }
113011
113319
  async function uploadAttachmentMsg(_cloud, senderPubkey, senderPrivkey, receiverChatPK, attachment = {}) {
113012
113320
  if (!senderPrivkey || !senderPubkey) {
@@ -113020,95 +113328,96 @@ async function uploadAttachmentMsg(_cloud, senderPubkey, senderPrivkey, receiver
113020
113328
  if (!chatId) {
113021
113329
  throw new Error("chat id required");
113022
113330
  }
113023
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
113024
113331
  const type = cleanText(attachment?.type) || "file";
113025
113332
  const data = attachment?.data;
113026
113333
  const meta = attachment?.meta || {};
113027
113334
  switch (type) {
113028
113335
  case "img":
113029
- return putImg(pair, nextCid, data, meta);
113336
+ return putImg(chatId, nextCid, data, meta);
113030
113337
  case "gif":
113031
- return putGif(pair, nextCid, data, meta);
113338
+ return putGif(chatId, nextCid, data, meta);
113032
113339
  case "m4a":
113033
- return putM4a(pair, nextCid, data, meta);
113340
+ return putM4a(chatId, nextCid, data, meta);
113034
113341
  case "mp4":
113035
- return putMp4(pair, nextCid, data, meta);
113342
+ return putMp4(chatId, nextCid, data, meta);
113036
113343
  case "file":
113037
- return putFile(pair, nextCid, data, meta);
113344
+ return putFile(chatId, nextCid, data, meta);
113038
113345
  default:
113039
- return putAttachment(pair, nextCid, type, data, meta);
113346
+ return putAttachment(chatId, nextCid, type, data, meta);
113040
113347
  }
113041
113348
  }
113042
113349
  async function updateMsg(cloud, chatId, msgId, senderPubkey, senderPrivkey, receiverChatPK, newMessage, options = {}) {
113043
113350
  if (!senderPubkey || !senderPrivkey)
113044
113351
  throw new Error("vault locked");
113045
- const pair = await getCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId });
113046
- if (pair.chatId !== chatId) {
113047
- throw new Error("chat mismatch");
113048
- }
113049
- const target = cleanText(newMessage?.cid) || cleanText(msgId);
113050
- if (!target) {
113051
- throw new Error("message target required");
113052
- }
113053
- const op = newMessage?.t === "req" && cleanText(newMessage?.tx) ? CHAT_ACTION_OPS.PAY_CONFIRM : CHAT_ACTION_OPS.EDIT;
113054
- const tsMs = Date.now();
113055
- const action = {
113056
- ...newMessage || {},
113057
- cid: makeCid(),
113058
- s: senderPubkey
113059
- };
113060
- const { head, body } = await sealMsg(pair, action, { op, target, ts: tsMs });
113061
- const messageId = head.cid;
113062
- const updatePreview = options?.updatePreview === true;
113063
- const preview2 = updatePreview ? ownerEditedPreview(senderPubkey, newMessage) : null;
113064
- const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
113065
- peerUid: cleanText(options?.receiverUid),
113066
- peerSigningPublicKey: cleanText(options?.peerSigningPublicKey),
113067
- entry: options?.ownEntry,
113068
- preview: preview2,
113069
- ts: preview2?.ts ?? tsMs
113070
- }) : null;
113071
- await cloud.chat.messages.send({
113072
- chatId,
113073
- messageId,
113074
- message: {
113075
- head,
113076
- body,
113077
- ttlMs: null
113078
- },
113079
- ownerEntry,
113080
- inbox: null
113352
+ return withCachedPair(senderPubkey, senderPrivkey, receiverChatPK, { chatId }, async (pair) => {
113353
+ if (pair.chatId !== chatId) {
113354
+ throw new Error("chat mismatch");
113355
+ }
113356
+ const target = cleanText(newMessage?.cid) || cleanText(msgId);
113357
+ if (!target) {
113358
+ throw new Error("message target required");
113359
+ }
113360
+ const op = newMessage?.t === "req" && cleanText(newMessage?.tx) ? CHAT_ACTION_OPS.PAY_CONFIRM : CHAT_ACTION_OPS.EDIT;
113361
+ const tsMs = Date.now();
113362
+ const action = {
113363
+ ...newMessage || {},
113364
+ cid: makeCid(),
113365
+ s: senderPubkey
113366
+ };
113367
+ const { head, body } = await sealMsg(pair, action, { op, target, ts: tsMs });
113368
+ const messageId = head.cid;
113369
+ const updatePreview = options?.updatePreview === true;
113370
+ const preview2 = updatePreview ? ownerEditedPreview(senderPubkey, newMessage) : null;
113371
+ const ownerEntry = updatePreview ? await ownEntryWrite(cloud, cleanText(options?.senderUid), senderPrivkey, pair, {
113372
+ peerUid: cleanText(options?.receiverUid),
113373
+ peerSigningPublicKey: cleanText(options?.peerSigningPublicKey),
113374
+ entry: options?.ownEntry,
113375
+ preview: preview2,
113376
+ ts: preview2?.ts ?? tsMs
113377
+ }) : null;
113378
+ await cloud.chat.messages.send({
113379
+ chatId,
113380
+ messageId,
113381
+ message: {
113382
+ head,
113383
+ body,
113384
+ ttlMs: null
113385
+ },
113386
+ ownerEntry,
113387
+ inbox: null
113388
+ });
113389
+ return {
113390
+ chatId,
113391
+ msgId: messageId,
113392
+ cid: head.cid,
113393
+ preview: preview2
113394
+ };
113081
113395
  });
113082
- return {
113083
- chatId,
113084
- msgId: messageId,
113085
- cid: head.cid,
113086
- preview: preview2
113087
- };
113088
113396
  }
113089
113397
  async function deleteMsg(cloud, chatId, messageOrId, senderPubkey, senderPrivkey, peerChatPK, options = {}) {
113090
113398
  if (!cloud || !chatId || !messageOrId || !senderPubkey || !senderPrivkey || !peerChatPK) {
113091
113399
  return false;
113092
113400
  }
113093
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
113094
- if (pair.chatId !== chatId) {
113095
- return false;
113096
- }
113097
- const item = messageDeleteItems([messageOrId])[0];
113098
- const target = cleanText(options?.docId) || cleanText(item?.id);
113099
- if (!target || target.startsWith("local:")) {
113100
- return false;
113101
- }
113102
- const deleteTarget = cleanText(options?.target) || cleanText(getMessageKey(messageOrId)) || target;
113103
- await sendDeleteAction(cloud, senderPubkey, senderPrivkey, peerChatPK, deleteTarget, {
113104
- chatId,
113105
- linkId: options?.linkId,
113106
- senderUid: options?.senderUid
113107
- });
113108
- await cloud.chat.messages.delete(chatId, target, {
113109
- mediaPaths: item?.mediaPath ? [item.mediaPath] : []
113401
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
113402
+ if (pair.chatId !== chatId) {
113403
+ return false;
113404
+ }
113405
+ const item = messageDeleteItems([messageOrId])[0];
113406
+ const target = cleanText(options?.docId) || cleanText(item?.id);
113407
+ if (!target || target.startsWith("local:")) {
113408
+ return false;
113409
+ }
113410
+ const deleteTarget = cleanText(options?.target) || cleanText(getMessageKey(messageOrId)) || target;
113411
+ await sendDeleteAction(cloud, senderPubkey, senderPrivkey, peerChatPK, deleteTarget, {
113412
+ chatId,
113413
+ linkId: options?.linkId,
113414
+ senderUid: options?.senderUid
113415
+ });
113416
+ await cloud.chat.messages.delete(chatId, target, {
113417
+ mediaPaths: item?.mediaPath ? [item.mediaPath] : []
113418
+ });
113419
+ return true;
113110
113420
  });
113111
- return true;
113112
113421
  }
113113
113422
  async function deleteMsgs(cloud, chatId, messages, senderPubkey, senderPrivkey, peerChatPK) {
113114
113423
  if (!cloud || !chatId || !senderPubkey || !senderPrivkey || !peerChatPK) {
@@ -113118,31 +113427,31 @@ async function deleteMsgs(cloud, chatId, messages, senderPubkey, senderPrivkey,
113118
113427
  if (!items.length) {
113119
113428
  return 0;
113120
113429
  }
113121
- const pair = await getCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId });
113122
- if (pair.chatId !== chatId) {
113123
- return 0;
113124
- }
113125
- let deleted = 0;
113126
- for (let index = 0;index < items.length; index += DELETE_WRITE_BATCH_SIZE) {
113127
- const chunk = items.slice(index, index + DELETE_WRITE_BATCH_SIZE);
113128
- const targets = chunk.map((item) => cleanText(item.id)).filter((target) => target && !target.startsWith("local:"));
113129
- const mediaPaths = chunk.map((item) => cleanText(item.mediaPath)).filter(Boolean);
113130
- deleted += await cloud.chat.messages.deleteMany(chatId, targets, { mediaPaths });
113131
- }
113132
- return deleted;
113430
+ return withCachedPair(senderPubkey, senderPrivkey, peerChatPK, { chatId }, async (pair) => {
113431
+ if (pair.chatId !== chatId) {
113432
+ return 0;
113433
+ }
113434
+ let deleted = 0;
113435
+ for (let index = 0;index < items.length; index += DELETE_WRITE_BATCH_SIZE) {
113436
+ const chunk = items.slice(index, index + DELETE_WRITE_BATCH_SIZE);
113437
+ const targets = chunk.map((item) => cleanText(item.id)).filter((target) => target && !target.startsWith("local:"));
113438
+ const mediaPaths = chunk.map((item) => cleanText(item.mediaPath)).filter(Boolean);
113439
+ deleted += await cloud.chat.messages.deleteMany(chatId, targets, { mediaPaths });
113440
+ }
113441
+ return deleted;
113442
+ });
113133
113443
  }
113134
- async function readMsgMedia(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg) {
113135
- return readMsgAttachment2(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg);
113444
+ async function readMsgMedia(readChatMedia, msg) {
113445
+ return readMsgAttachment2(readChatMedia, msg);
113136
113446
  }
113137
- async function readMsgAttachment2(readChatMedia, userChatPK, userPrivKey, peerChatPK, msg) {
113447
+ async function readMsgAttachment2(readChatMedia, msg) {
113138
113448
  if (typeof readChatMedia !== "function") {
113139
113449
  throw new Error("chat media read required");
113140
113450
  }
113141
- if (!userChatPK || !userPrivKey || !peerChatPK || !msg) {
113451
+ if (!msg) {
113142
113452
  return null;
113143
113453
  }
113144
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: msg?.chatId || messageFileChatId(msg) });
113145
- return readMsgFile(readChatMedia, pair, msg);
113454
+ return readMsgFile(readChatMedia, msg);
113146
113455
  }
113147
113456
  var DELETE_WRITE_BATCH_SIZE;
113148
113457
  var init_write = __esm(() => {
@@ -113385,14 +113694,14 @@ function hasInvalidStoredMediaRef(message) {
113385
113694
  return true;
113386
113695
  }
113387
113696
  }
113388
- function readMessageMedia(cloud, media, chatPK, chatPrivateKey, peerChatPK, message) {
113697
+ function readMessageMedia(cloud, media, message) {
113389
113698
  const readChatMedia = cloud?.chat?.media?.read;
113390
113699
  if (typeof media?.readMessageFile === "function") {
113391
- return media.readMessageFile(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
113700
+ return media.readMessageFile(readChatMedia, message);
113392
113701
  }
113393
- return readMsgMedia(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
113702
+ return readMsgMedia(readChatMedia, message);
113394
113703
  }
113395
- function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey, localCache }) {
113704
+ function createChatSave({ cloud, media = {}, chatBanned, localCache }) {
113396
113705
  const makeMessagePermanent = async (chatId, message) => {
113397
113706
  if (chatBanned) {
113398
113707
  throw makeChatUnavailableError();
@@ -113410,7 +113719,7 @@ function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey,
113410
113719
  const updated = await makeMsgTemporary(cloud, chatId, list, ttl);
113411
113720
  return { updated, ttl };
113412
113721
  };
113413
- const readMessageFile = async (peerChatPK, message) => {
113722
+ const readMessageFile = async (_peerChatPK, message) => {
113414
113723
  if (chatBanned) {
113415
113724
  throw makeChatUnavailableError();
113416
113725
  }
@@ -113432,7 +113741,7 @@ function createChatSave({ cloud, media = {}, chatBanned, chatPK, chatPrivateKey,
113432
113741
  throw makeFileGoneError();
113433
113742
  }
113434
113743
  try {
113435
- const bytes = await readMessageMedia(cloud, media, chatPK, chatPrivateKey, peerChatPK, message);
113744
+ const bytes = await readMessageMedia(cloud, media, message);
113436
113745
  saveMedia(localCache, message, bytes, message);
113437
113746
  return bytes;
113438
113747
  } catch (error) {
@@ -113499,7 +113808,7 @@ function messageExpiryGroups(messages, selfChatPublicKey, seenAt) {
113499
113808
  { messages: day, expiresAt: seenAt + AFTER_SEEN_MS }
113500
113809
  ].filter((group) => group.messages.length);
113501
113810
  }
113502
- function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localCache, pendingReadRef, readCacheRef, readWriteInterval, getChatRetention, setChats, diag }) {
113811
+ function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localCache, pendingReadRef, readCacheRef, readWriteInterval, getChatRetention, sendOptionsForPeer, setChats, diag }) {
113503
113812
  const scheduleRead = (chatId, message, previewMs, sendReceipt, expiryMessages = []) => {
113504
113813
  scheduleReadWrite({
113505
113814
  pendingRead: pendingReadRef.current,
@@ -113510,24 +113819,37 @@ function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localC
113510
113819
  expiryMessages: sendReceipt ? expiryMessages : [],
113511
113820
  interval: readWriteInterval,
113512
113821
  write: async (pending) => {
113822
+ let receiptError = null;
113823
+ let receiptResult = null;
113824
+ let introduceSigner = false;
113825
+ if (pending.receipt) {
113826
+ const sendOptions = sendOptionsForPeer(pending.receipt.peerChatPK);
113827
+ introduceSigner = sendOptions?.signerShared !== true;
113828
+ try {
113829
+ receiptResult = await sendReadReceipt(cloud, chatPK, chatPrivateKey, pending.receipt.peerChatPK, pending.receipt.target, {
113830
+ chatId,
113831
+ retention: getChatRetention(chatId),
113832
+ senderUid: uid,
113833
+ receiverUid: sendOptions?.receiverUid,
113834
+ ping: introduceSigner
113835
+ });
113836
+ } catch (error) {
113837
+ receiptError = error;
113838
+ }
113839
+ }
113513
113840
  const writes = [
113514
113841
  setChatRead(cloud, uid, chatPrivateKey, chatId, pending.previewMs, {
113515
- preview: (currentPreview) => withChatPreviewOpened(currentPreview, pending.message, pending.previewMs, chatPK) || currentPreview
113842
+ preview: (currentPreview) => withChatPreviewOpened(currentPreview, pending.message, pending.previewMs, chatPK) || currentPreview,
113843
+ signerShared: introduceSigner && receiptResult?.delivered === true
113516
113844
  })
113517
113845
  ];
113518
113846
  if (pending.receipt) {
113519
- writes.push(sendReadReceipt(cloud, chatPK, chatPrivateKey, pending.receipt.peerChatPK, pending.receipt.target, {
113520
- chatId,
113521
- retention: getChatRetention(chatId),
113522
- senderUid: uid
113523
- }));
113524
113847
  for (const group of messageExpiryGroups(pending.expiryMessages, chatPK, pending.expirySeenAt)) {
113525
113848
  writes.push(setMessageExpiry(cloud, chatId, group.messages, group.expiresAt));
113526
113849
  }
113527
113850
  }
113528
113851
  const results = await Promise.allSettled(writes);
113529
113852
  const ownerError = results[0]?.status === "rejected" ? results[0].reason : null;
113530
- const receiptError = pending.receipt && results[1]?.status === "rejected" ? results[1].reason : null;
113531
113853
  if (ownerError) {
113532
113854
  markError(diag, "chat.read.state.write", Date.now(), ownerError);
113533
113855
  }
@@ -113535,7 +113857,7 @@ function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localC
113535
113857
  readCacheRef.current.delete(chatId);
113536
113858
  markError(diag, "chat.read.receipt.write", Date.now(), receiptError);
113537
113859
  }
113538
- for (const result of results.slice(pending.receipt ? 2 : 1)) {
113860
+ for (const result of results.slice(1)) {
113539
113861
  if (result?.status === "rejected") {
113540
113862
  markError(diag, "chat.message.expiry.write", Date.now(), result.reason);
113541
113863
  }
@@ -114078,8 +114400,11 @@ function withSharedMediaUpload(cloud, media, meta = {}) {
114078
114400
  };
114079
114401
  }
114080
114402
  function uploadMessageAttachment(cloud, media, senderPubkey, senderPrivkey, receiverChatPK, attachment = {}) {
114403
+ if (!senderPubkey || !senderPrivkey) {
114404
+ throw new Error("vault locked");
114405
+ }
114081
114406
  if (typeof media?.uploadAttachment === "function") {
114082
- return media.uploadAttachment(senderPubkey, senderPrivkey, receiverChatPK, attachment);
114407
+ return media.uploadAttachment(attachment);
114083
114408
  }
114084
114409
  return uploadAttachmentMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, withMediaUpload(cloud, media, attachment));
114085
114410
  }
@@ -114377,17 +114702,21 @@ ${cid}` : "";
114377
114702
  const requiredPreview = previewRequired || sendOptions.chatExists !== true;
114378
114703
  let local = await showLocalMessage(peerChatPK, message, sendOptions);
114379
114704
  let committed = false;
114705
+ let sent = false;
114706
+ const markSent = () => {
114707
+ if (sent)
114708
+ return;
114709
+ sent = true;
114710
+ sentChatIdsRef.current.add(local.chatId);
114711
+ markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
114712
+ };
114380
114713
  const markCommitted = () => {
114381
114714
  if (committed)
114382
114715
  return;
114383
114716
  committed = true;
114384
- sentChatIdsRef.current.add(local.chatId);
114717
+ markSent();
114385
114718
  markDone(diag, "chat.send.commit", startedAt, { type: local?.t || "txt", previewRequired: !!requiredPreview });
114386
114719
  };
114387
- const markSent = () => {
114388
- sentChatIdsRef.current.add(local.chatId);
114389
- markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
114390
- };
114391
114720
  return await new Promise((resolve2, reject) => {
114392
114721
  const job = {
114393
114722
  previewKey: local.chatId,
@@ -114605,7 +114934,8 @@ ${cid}` : "";
114605
114934
  linkId: local.linkId,
114606
114935
  version: local.version,
114607
114936
  sendOptions,
114608
- nextAttachment: prepared.nextAttachment
114937
+ nextAttachment: prepared.nextAttachment,
114938
+ committed: false
114609
114939
  };
114610
114940
  }));
114611
114941
  return await new Promise((resolve2, reject) => {
@@ -114614,7 +114944,9 @@ ${cid}` : "";
114614
114944
  reject,
114615
114945
  onError: () => {
114616
114946
  for (const item of locals) {
114617
- markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114947
+ if (!item.committed) {
114948
+ markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114949
+ }
114618
114950
  }
114619
114951
  },
114620
114952
  run: async () => {
@@ -114629,6 +114961,7 @@ ${cid}` : "";
114629
114961
  results.push({ peerChatPK: item.peerChatPK, ok: false, error: uploadError });
114630
114962
  continue;
114631
114963
  }
114964
+ let sentMessage;
114632
114965
  try {
114633
114966
  let uploaded = uploads.get(uploadKey);
114634
114967
  if (!uploaded) {
@@ -114641,17 +114974,32 @@ ${cid}` : "";
114641
114974
  cid: item.cid,
114642
114975
  s: chatPK
114643
114976
  };
114644
- const sentMessage = withMessageRetention(sent, item.sendOptions.retention);
114977
+ sentMessage = withMessageRetention(sent, item.sendOptions.retention);
114645
114978
  rememberCachedLocalMedia(item.chatId, item.cid, sentMessage);
114646
- await sendMsg(cloud, chatPK, chatPrivateKey, item.peerChatPK, sentMessage, { ...item.sendOptions, chatId: item.chatId, linkId: item.linkId, senderUid: uid });
114647
- markLocalStatus(item.chatId, item.cid, LOCAL_SENT);
114979
+ await sendMsg(cloud, chatPK, chatPrivateKey, item.peerChatPK, sentMessage, {
114980
+ ...item.sendOptions,
114981
+ chatId: item.chatId,
114982
+ linkId: item.linkId,
114983
+ senderUid: uid,
114984
+ onCommitted: () => {
114985
+ if (item.committed)
114986
+ return;
114987
+ item.committed = true;
114988
+ sentChatIdsRef.current.add(item.chatId);
114989
+ markLocalStatus(item.chatId, item.cid, LOCAL_SENT);
114990
+ }
114991
+ });
114648
114992
  results.push({ peerChatPK: item.peerChatPK, ok: true, message: sentMessage });
114649
114993
  } catch (error) {
114650
114994
  if (!uploads.has(uploadKey)) {
114651
114995
  uploadErrors.set(uploadKey, error);
114652
114996
  }
114653
- markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
114654
- results.push({ peerChatPK: item.peerChatPK, ok: false, error });
114997
+ if (item.committed) {
114998
+ results.push({ peerChatPK: item.peerChatPK, ok: true, message: sentMessage });
114999
+ } else {
115000
+ markLocalStatus(item.chatId, item.cid, LOCAL_FAILED);
115001
+ results.push({ peerChatPK: item.peerChatPK, ok: false, error });
115002
+ }
114655
115003
  }
114656
115004
  }
114657
115005
  return results;
@@ -115458,7 +115806,6 @@ async function decryptRecordEntries(records, userChatPK, userPrivKey, peerChatPK
115458
115806
  return [];
115459
115807
  }
115460
115808
  const signingKeysVersion = signingKeysFingerprint(options?.signingKeysByChatKey);
115461
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId });
115462
115809
  const entryOptions = { ...options, signingKeysVersion };
115463
115810
  const shouldYield = positiveInt(options?.yieldEvery, 0) > 0;
115464
115811
  const entries = new Array(source.length).fill(null);
@@ -115475,38 +115822,40 @@ async function decryptRecordEntries(records, userChatPK, userPrivKey, peerChatPK
115475
115822
  }
115476
115823
  pending.push({ record, index });
115477
115824
  };
115478
- const openPending = async (pending) => {
115479
- if (!pending.length) {
115480
- return;
115481
- }
115482
- const opened = await decryptRecordBatchWithPair(pending.map((item) => item.record), pair, cache, entryOptions);
115483
- for (let index = 0;index < opened.length; index += 1) {
115484
- const entry = opened[index];
115485
- if (entry) {
115486
- entries[pending[index].index] = entry;
115825
+ return withCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId }, async (pair) => {
115826
+ const openPending = async (pending) => {
115827
+ if (!pending.length) {
115828
+ return;
115829
+ }
115830
+ const opened = await decryptRecordBatchWithPair(pending.map((item) => item.record), pair, cache, entryOptions);
115831
+ for (let index = 0;index < opened.length; index += 1) {
115832
+ const entry = opened[index];
115833
+ if (entry) {
115834
+ entries[pending[index].index] = entry;
115835
+ }
115487
115836
  }
115837
+ };
115838
+ if (!options?.signal && !shouldYield) {
115839
+ const pending = [];
115840
+ for (let index = 0;index < source.length; index += 1) {
115841
+ collectRecord(source[index], index, pending);
115842
+ }
115843
+ await openPending(pending);
115844
+ return entries.filter(Boolean);
115488
115845
  }
115489
- };
115490
- if (!options?.signal && !shouldYield) {
115491
- const pending = [];
115492
- for (let index = 0;index < source.length; index += 1) {
115493
- collectRecord(source[index], index, pending);
115846
+ const chunkSize = positiveInt(options?.yieldEvery, source.length || 1);
115847
+ for (let index = 0;index < source.length; index += chunkSize) {
115848
+ throwIfAborted(options?.signal);
115849
+ const chunk = source.slice(index, index + chunkSize);
115850
+ const pending = [];
115851
+ for (let offset = 0;offset < chunk.length; offset += 1) {
115852
+ collectRecord(chunk[offset], index + offset, pending);
115853
+ }
115854
+ await openPending(pending);
115855
+ await maybeYield(index + chunk.length, options);
115494
115856
  }
115495
- await openPending(pending);
115496
115857
  return entries.filter(Boolean);
115497
- }
115498
- const chunkSize = positiveInt(options?.yieldEvery, source.length || 1);
115499
- for (let index = 0;index < source.length; index += chunkSize) {
115500
- throwIfAborted(options?.signal);
115501
- const chunk = source.slice(index, index + chunkSize);
115502
- const pending = [];
115503
- for (let offset = 0;offset < chunk.length; offset += 1) {
115504
- collectRecord(chunk[offset], index + offset, pending);
115505
- }
115506
- await openPending(pending);
115507
- await maybeYield(index + chunk.length, options);
115508
- }
115509
- return entries.filter(Boolean);
115858
+ });
115510
115859
  }
115511
115860
  function messageQueryLimit(pageSize) {
115512
115861
  return positiveInt(pageSize, MSG_BATCH_SIZE);
@@ -115559,7 +115908,11 @@ function readableEntryWindow(entries, pageSize, chatPK, peerChatPK, options = {}
115559
115908
  const limitCount = positiveInt(pageSize, MSG_BATCH_SIZE);
115560
115909
  const projectedEntries = readableProjectedEntries(source, chatPK, peerChatPK, options);
115561
115910
  if (!projectedEntries.length) {
115562
- return { count: 0, entries: [], start: 0 };
115911
+ return {
115912
+ count: 0,
115913
+ entries: source.filter((entry) => isProjectionSupportMsg(entry?.message)),
115914
+ start: 0
115915
+ };
115563
115916
  }
115564
115917
  const indexes = projectedEntries.map((entry) => entry.index);
115565
115918
  const projectedByIndex = new Map(projectedEntries.map((entry) => [entry.index, entry.message]));
@@ -115852,9 +116205,10 @@ async function decryptMsg(msgData, userChatPK, userPrivKey, peerChatPK, options
115852
116205
  return null;
115853
116206
  }
115854
116207
  try {
115855
- const pair = await getCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId });
115856
- const message = await openMsg(pair, msgData, { signingKeysByChatKey: options?.signingKeysByChatKey, crypto: options?.crypto });
115857
- return normalizeDecryptedMsg(msgData, message);
116208
+ return await withCachedPair(userChatPK, userPrivKey, peerChatPK, { chatId: options?.chatId }, async (pair) => {
116209
+ const message = await openMsg(pair, msgData, { signingKeysByChatKey: options?.signingKeysByChatKey, crypto: options?.crypto });
116210
+ return normalizeDecryptedMsg(msgData, message);
116211
+ });
115858
116212
  } catch {
115859
116213
  return null;
115860
116214
  }
@@ -116924,12 +117278,12 @@ function warmKeyStore(ref) {
116924
117278
  }
116925
117279
  return ref.current;
116926
117280
  }
116927
- function readMessageMedia2(cloud, media, chatPK, chatPrivateKey, peerChatPK, message) {
117281
+ function readMessageMedia2(cloud, media, message) {
116928
117282
  const readChatMedia = cloud?.chat?.media?.read;
116929
117283
  if (typeof media?.readMessageFile === "function") {
116930
- return media.readMessageFile(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
117284
+ return media.readMessageFile(readChatMedia, message);
116931
117285
  }
116932
- return readMsgMedia(readChatMedia, chatPK, chatPrivateKey, peerChatPK, message);
117286
+ return readMsgMedia(readChatMedia, message);
116933
117287
  }
116934
117288
  function isDenied3(error) {
116935
117289
  return error?.code === "permission-denied";
@@ -117136,12 +117490,12 @@ function createChatMessageBatches({ cloud, media = {}, chatPK, chatPrivateKey, c
117136
117490
  return;
117137
117491
  }
117138
117492
  const tasks = buildMediaTasks();
117139
- const readFile4 = async (peerChatPK, message) => {
117493
+ const readFile4 = async (_peerChatPK, message) => {
117140
117494
  const cached = await readCachedMedia(localCache, message);
117141
117495
  if (cached?.byteLength) {
117142
117496
  return cached;
117143
117497
  }
117144
- const bytes = await readMessageMedia2(cloud, media, chatPK, chatPrivateKey, peerChatPK, message);
117498
+ const bytes = await readMessageMedia2(cloud, media, message);
117145
117499
  if (bytes?.byteLength) {
117146
117500
  saveMedia(localCache, message, bytes, message);
117147
117501
  }
@@ -117888,6 +118242,16 @@ function pingSigningKeys(ping, existing) {
117888
118242
  [ping.payload.senderChatPK]: ping.payload.signingPublicKey
117889
118243
  };
117890
118244
  }
118245
+ function chatSettingsFromPreview(settings, preview2) {
118246
+ const current = normalizeChatSettings(settings);
118247
+ if (!isSystemMsg(preview2)) {
118248
+ return current;
118249
+ }
118250
+ return {
118251
+ ...current,
118252
+ retention: cleanChatRetention(preview2.retention)
118253
+ };
118254
+ }
117891
118255
  function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options = {}) {
117892
118256
  const ts = timestampMs(ms, null) ?? timestampMs(existing?.ts, null) ?? timestampMs(preview2?.ts, null) ?? 0;
117893
118257
  if (!ts) {
@@ -117904,10 +118268,11 @@ function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options
117904
118268
  peerChatPK: ping.payload.senderChatPK,
117905
118269
  peerUid: existing?.peerUid || cleanText(ping.payload.senderUid) || null,
117906
118270
  signingKeysByChatKey: pingSigningKeys(ping, existing),
117907
- settings: existing?.settings,
118271
+ settings: chatSettingsFromPreview(existing?.settings, preview2),
117908
118272
  saved: existing?.saved || null,
117909
118273
  readMs,
117910
118274
  startMs,
118275
+ signerShared: existing?.signerShared === true,
117911
118276
  preview: visiblePreview,
117912
118277
  ts,
117913
118278
  unseen: visiblePreview ? isChatUnseenForUser({ preview: visiblePreview, readMs }, userChatPK) : false
@@ -117963,10 +118328,11 @@ async function savePing(cloud, uid, userChatPK, userPrivKey, ping, options = {})
117963
118328
  peerUid: peerUid2 || existing?.peerUid,
117964
118329
  peerSigningPublicKey: ping.payload.signingPublicKey || existing?.signingKeysByChatKey?.[ping.payload.senderChatPK],
117965
118330
  signingKeysByChatKey,
117966
- settings: existing?.settings,
118331
+ settings: chatSettingsFromPreview(existing?.settings, preview2),
117967
118332
  preview: preview2 || existing?.preview,
117968
118333
  readMs: existing?.readMs,
117969
- startMs: existing ? existing.startMs : timestampMs(options.startPreview?.ts ?? preview2?.ts, null, { positive: true })
118334
+ startMs: existing ? existing.startMs : timestampMs(options.startPreview?.ts ?? preview2?.ts, null, { positive: true }),
118335
+ signerShared: existing?.signerShared === true
117970
118336
  });
117971
118337
  const body = await sealOwnChatEntry(userPrivKey, entryId, entry);
117972
118338
  const record = { body };
@@ -117976,14 +118342,16 @@ async function savePing(cloud, uid, userChatPK, userPrivKey, ping, options = {})
117976
118342
  await cloud.user.chats.write(uid, entryId, record);
117977
118343
  return true;
117978
118344
  }
117979
- function chatWithReadPreview(existing, preview2, userChatPK) {
117980
- if (!existing?.id || !preview2) {
118345
+ function chatWithReadPreview(existing, preview2, userChatPK, fields = {}) {
118346
+ if (!existing?.id) {
117981
118347
  return null;
117982
118348
  }
117983
118349
  return {
117984
118350
  ...existing,
117985
- preview: preview2,
117986
- unseen: isChatUnseenForUser({ preview: preview2, readMs: existing.readMs }, userChatPK)
118351
+ signingKeysByChatKey: fields.signingKeysByChatKey || existing.signingKeysByChatKey,
118352
+ peerUid: fields.peerUid || existing.peerUid,
118353
+ preview: preview2 || existing.preview,
118354
+ unseen: preview2 ? isChatUnseenForUser({ preview: preview2, readMs: existing.readMs }, userChatPK) : existing.unseen
117987
118355
  };
117988
118356
  }
117989
118357
  function patchReadPingPreview(existing, receiptPreview, userChatPK) {
@@ -118064,6 +118432,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
118064
118432
  let current = chatsById.get(group.chatId) || null;
118065
118433
  let wrote = false;
118066
118434
  let handled = false;
118435
+ const identityPing = group.message?.ping || group.read?.ping || group.delete?.ping;
118436
+ const peerUid2 = await resolvePingUid(cloud, identityPing?.payload);
118067
118437
  if (group.message) {
118068
118438
  const item = group.message;
118069
118439
  const entryId = ownChatEntryId(userPrivKey, item.chatId);
@@ -118084,7 +118454,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
118084
118454
  signingKeysByChatKey,
118085
118455
  preview: preview2,
118086
118456
  startPreview,
118087
- tsMs: item.ms
118457
+ tsMs: item.ms,
118458
+ peerUid: peerUid2
118088
118459
  });
118089
118460
  }
118090
118461
  if (group.read) {
@@ -118094,22 +118465,24 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
118094
118465
  const signingKeysByChatKey = pingSigningKeys(item.ping, existing);
118095
118466
  const receiptPreview = await readPingMsg(cloud, userChatPK, userPrivKey, item.ping, signingKeysByChatKey);
118096
118467
  const openedPreview = patchReadPingPreview(existing, receiptPreview, userChatPK);
118097
- const chat = chatWithReadPreview(current, openedPreview, userChatPK);
118468
+ const chat = chatWithReadPreview(current || existing, openedPreview, userChatPK, {
118469
+ signingKeysByChatKey,
118470
+ peerUid: peerUid2
118471
+ });
118098
118472
  handled = true;
118099
- if (openedPreview) {
118100
- if (chat) {
118101
- current = chat;
118102
- chatsById.set(chat.id, chat);
118103
- options.onPingChat?.(chat);
118104
- }
118105
- const writeExisting = chat || existing;
118106
- wrote = await savePing(cloud, uid, userChatPK, userPrivKey, item.ping, {
118107
- existing: writeExisting,
118108
- signingKeysByChatKey,
118109
- preview: openedPreview,
118110
- writeTs: false
118111
- }) || wrote;
118473
+ if (chat) {
118474
+ current = chat;
118475
+ chatsById.set(chat.id, chat);
118476
+ options.onPingChat?.(chat);
118112
118477
  }
118478
+ const writeExisting = chat || existing;
118479
+ wrote = await savePing(cloud, uid, userChatPK, userPrivKey, item.ping, {
118480
+ existing: writeExisting,
118481
+ signingKeysByChatKey,
118482
+ preview: openedPreview || existing?.preview || null,
118483
+ writeTs: false,
118484
+ peerUid: peerUid2
118485
+ }) || wrote;
118113
118486
  }
118114
118487
  if (group.delete) {
118115
118488
  const item = group.delete;
@@ -118130,7 +118503,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
118130
118503
  wrote = await savePing(cloud, uid, userChatPK, userPrivKey, item.ping, {
118131
118504
  existing: writeExisting,
118132
118505
  signingKeysByChatKey,
118133
- preview: deletedPreview
118506
+ preview: deletedPreview,
118507
+ peerUid: peerUid2
118134
118508
  }) || wrote;
118135
118509
  }
118136
118510
  }
@@ -118152,6 +118526,7 @@ var init_inbox = __esm(() => {
118152
118526
  init_ping();
118153
118527
  init_query();
118154
118528
  init_chats();
118529
+ init_ttl();
118155
118530
  init_time();
118156
118531
  init_protocol();
118157
118532
  });
@@ -118535,6 +118910,7 @@ async function decryptChatEntry(entryRecord, userChatPK, userPrivKey) {
118535
118910
  saved: entry.saved || null,
118536
118911
  readMs,
118537
118912
  startMs,
118913
+ signerShared: entry.signerShared === true,
118538
118914
  preview: visiblePreview,
118539
118915
  ts,
118540
118916
  unseen: visiblePreview ? isChatUnseenForUser({ preview: visiblePreview, readMs }, userChatPK) : false
@@ -118647,6 +119023,7 @@ function createChatList({
118647
119023
  let chats = [];
118648
119024
  let peers = [];
118649
119025
  let isChatDataReady = false;
119026
+ let isChatListLive = false;
118650
119027
  let chatSourceReady = false;
118651
119028
  let lastChat = null;
118652
119029
  let serverChatIds = [];
@@ -118683,6 +119060,7 @@ function createChatList({
118683
119060
  chats,
118684
119061
  peers,
118685
119062
  isChatDataReady,
119063
+ isChatListLive,
118686
119064
  hasChats: chats.length > 0,
118687
119065
  previewNow,
118688
119066
  lastChat,
@@ -118716,6 +119094,13 @@ function createChatList({
118716
119094
  isChatDataReady = value;
118717
119095
  publish();
118718
119096
  };
119097
+ const setIsChatListLive = (next) => {
119098
+ const value = nextValue(isChatListLive, next);
119099
+ if (Object.is(isChatListLive, value))
119100
+ return;
119101
+ isChatListLive = value;
119102
+ publish();
119103
+ };
118719
119104
  const setLastChat = (next) => {
118720
119105
  const value = nextValue(lastChat, next);
118721
119106
  if (Object.is(lastChat, value))
@@ -118925,6 +119310,7 @@ function createChatList({
118925
119310
  const resetChatList = (ready = false) => {
118926
119311
  chatSourceReady = ready;
118927
119312
  syncChatDataReady();
119313
+ setIsChatListLive(false);
118928
119314
  setChats((prev) => prev.length ? [] : prev);
118929
119315
  setPeers((prev) => prev.length ? [] : prev);
118930
119316
  setLastChat((prev) => prev ? null : prev);
@@ -119014,6 +119400,7 @@ function createChatList({
119014
119400
  resetAllChatState();
119015
119401
  return;
119016
119402
  }
119403
+ setIsChatListLive(false);
119017
119404
  let cancelled = false;
119018
119405
  const hydrateKey = localCache?.id ? `${localCache.id}:${chatPK}` : "";
119019
119406
  if (hydrateKey && lastHydratedCacheKeyRef.current !== hydrateKey) {
@@ -119113,6 +119500,7 @@ function createChatList({
119113
119500
  writeCache: true,
119114
119501
  serverEndReached: cachedHistoryEndRef.current
119115
119502
  });
119503
+ setIsChatListLive(true);
119116
119504
  listenUpdateSeqRef.current += 1;
119117
119505
  markDiag(diag, "chat.provider.listen.update", {
119118
119506
  seq: listenUpdateSeqRef.current,
@@ -119155,6 +119543,7 @@ function createChatList({
119155
119543
  markChatSourceReady();
119156
119544
  fillVisibleChatPage();
119157
119545
  }, (error) => {
119546
+ setIsChatListLive(false);
119158
119547
  markError(diag, "chat.provider.listen", listenStartedAt, error);
119159
119548
  if (shouldResetChatListAfterListenError(error)) {
119160
119549
  resetAllChatState();
@@ -119308,6 +119697,7 @@ function createChatList({
119308
119697
  chatExists: !!peerChat?.id || hasServerChatForPeer(peerChatPK),
119309
119698
  receiverUid: peerChat?.peerUid || "",
119310
119699
  peerSigningPublicKey: peerChat?.signingKeysByChatKey?.[peerChatPK] || "",
119700
+ signerShared: peerChat?.signerShared === true,
119311
119701
  ownEntry: peerChat?.entryId ? peerChat : null
119312
119702
  };
119313
119703
  };
@@ -120005,6 +120395,7 @@ function createChatSession({
120005
120395
  readCacheRef,
120006
120396
  readWriteInterval,
120007
120397
  getChatRetention,
120398
+ sendOptionsForPeer,
120008
120399
  setChats,
120009
120400
  diag
120010
120401
  });
@@ -121149,7 +121540,7 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
121149
121540
  }
121150
121541
  if (avatarVersion != null && typeof cloud.peer.avatar.read === "function") {
121151
121542
  try {
121152
- const bytes = await cloud.peer.avatar.read(uid);
121543
+ const bytes = await cloud.peer.avatar.read(uid, { version: avatarVersion });
121153
121544
  const cachedSource = await writeCachedAvatar(uid, avatarVersion, bytes);
121154
121545
  if (cachedSource) {
121155
121546
  return cachedSource;
@@ -121686,6 +122077,10 @@ function getSatsBalance(result) {
121686
122077
  }
121687
122078
  return satsBalance;
121688
122079
  }
122080
+ function displayBalanceValue(value) {
122081
+ const balance = Number(value);
122082
+ return Number.isSafeInteger(balance) && balance >= 0 ? balance : null;
122083
+ }
121689
122084
  function samePlainObject(a, b) {
121690
122085
  if (a === b) {
121691
122086
  return true;
@@ -121714,9 +122109,10 @@ function sameTokenBalances(a, b) {
121714
122109
  }
121715
122110
  return true;
121716
122111
  }
121717
- function createWalletBalance({ wallet, diag, onChange = null }) {
122112
+ function createWalletBalance({ wallet, diag, initialDisplayBalance = null, onBalance = null, onChange = null }) {
121718
122113
  let snapshot = {
121719
122114
  balance: null,
122115
+ displayBalance: displayBalanceValue(initialDisplayBalance),
121720
122116
  satsBalance: null,
121721
122117
  tokenBalances: new Map,
121722
122118
  balanceReady: false,
@@ -121726,10 +122122,28 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121726
122122
  snapshot = { ...snapshot, ...changes };
121727
122123
  onChange?.();
121728
122124
  };
122125
+ const publishDisplayBalance = (changes, value, trusted = false) => {
122126
+ const displayBalance = displayBalanceValue(value);
122127
+ if (displayBalance == null || !trusted && !snapshot.balanceReady) {
122128
+ return null;
122129
+ }
122130
+ if (!Object.is(snapshot.displayBalance, displayBalance)) {
122131
+ changes.displayBalance = displayBalance;
122132
+ }
122133
+ return displayBalance;
122134
+ };
121729
122135
  const setBalance = (nextBalance) => {
121730
122136
  const value = typeof nextBalance === "function" ? nextBalance(snapshot.balance) : nextBalance;
122137
+ const changes = {};
121731
122138
  if (!Object.is(snapshot.balance, value)) {
121732
- publish({ balance: value });
122139
+ changes.balance = value;
122140
+ }
122141
+ const displayBalance = publishDisplayBalance(changes, value);
122142
+ if (Object.keys(changes).length) {
122143
+ publish(changes);
122144
+ }
122145
+ if (displayBalance != null) {
122146
+ onBalance?.(displayBalance);
121733
122147
  }
121734
122148
  };
121735
122149
  const setTokenBalances = (nextTokenBalances) => {
@@ -121738,12 +122152,13 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121738
122152
  publish({ tokenBalances: new Map(source) });
121739
122153
  }
121740
122154
  };
121741
- const setBalanceResult = (result) => {
122155
+ const setBalanceResult = (result, { ready = false } = {}) => {
121742
122156
  const changes = {};
121743
122157
  const nextBalance = getBalanceValue(result);
121744
122158
  if (nextBalance != null && !Object.is(snapshot.balance, nextBalance)) {
121745
122159
  changes.balance = nextBalance;
121746
122160
  }
122161
+ const nextDisplayBalance = publishDisplayBalance(changes, nextBalance, true);
121747
122162
  const nextSatsBalance = getSatsBalance(result);
121748
122163
  if (nextSatsBalance && !samePlainObject(snapshot.satsBalance, nextSatsBalance)) {
121749
122164
  changes.satsBalance = nextSatsBalance;
@@ -121752,9 +122167,15 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121752
122167
  if (nextTokenBalances && !sameTokenBalances(snapshot.tokenBalances, nextTokenBalances)) {
121753
122168
  changes.tokenBalances = new Map(nextTokenBalances);
121754
122169
  }
122170
+ if (ready && !snapshot.balanceReady) {
122171
+ changes.balanceReady = true;
122172
+ }
121755
122173
  if (Object.keys(changes).length) {
121756
122174
  publish(changes);
121757
122175
  }
122176
+ if (nextDisplayBalance != null) {
122177
+ onBalance?.(nextDisplayBalance);
122178
+ }
121758
122179
  };
121759
122180
  const setSatsBalanceResult = (nextSatsBalance) => {
121760
122181
  if (!nextSatsBalance || typeof nextSatsBalance !== "object") {
@@ -121767,9 +122188,13 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121767
122188
  if (nextSatsBalance.available != null && !Object.is(snapshot.balance, nextSatsBalance.available)) {
121768
122189
  changes.balance = nextSatsBalance.available;
121769
122190
  }
122191
+ const nextDisplayBalance = publishDisplayBalance(changes, nextSatsBalance.available);
121770
122192
  if (Object.keys(changes).length) {
121771
122193
  publish(changes);
121772
122194
  }
122195
+ if (nextDisplayBalance != null) {
122196
+ onBalance?.(nextDisplayBalance);
122197
+ }
121773
122198
  };
121774
122199
  const getBalance = async () => {
121775
122200
  if (!wallet) {
@@ -121783,7 +122208,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121783
122208
  }
121784
122209
  try {
121785
122210
  const result = await wallet.getBalance();
121786
- setBalanceResult(result);
122211
+ setBalanceResult(result, { ready: true });
121787
122212
  markDone(diag, "wallet.balance", startedAt, { hasBalance: getBalanceValue(result) != null, hasSatsBalance: !!getSatsBalance(result), hasTokenBalances: !!getTokenBalances(result) });
121788
122213
  } catch (error) {
121789
122214
  markError(diag, "wallet.balance", startedAt, error);
@@ -121803,6 +122228,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121803
122228
  const reset = () => {
121804
122229
  snapshot = {
121805
122230
  balance: null,
122231
+ displayBalance: null,
121806
122232
  satsBalance: null,
121807
122233
  tokenBalances: new Map,
121808
122234
  balanceReady: false,
@@ -121819,7 +122245,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121819
122245
  setSatsBalanceResult
121820
122246
  };
121821
122247
  }
121822
- var init_balance = () => {};
122248
+ var init_balance2 = () => {};
121823
122249
 
121824
122250
  // ../../shared/wallet/deposits.js
121825
122251
  async function getClaimableDepositUtxos(wallet, options = {}) {
@@ -122844,11 +123270,6 @@ function createWalletPrivacy({ wallet, ghostWallet, diag }) {
122844
123270
  const startedAt = Date.now();
122845
123271
  const desired = desiredPrivacy;
122846
123272
  markDiag(diag, "wallet.privacy.start", { ghostWallet: desired });
122847
- const current = typeof wallet.getWalletSettings === "function" ? await wallet.getWalletSettings() : null;
122848
- if (closed || requestRevision !== revision || current?.privateEnabled === true === desired) {
122849
- markDone(diag, "wallet.privacy", startedAt, { changed: false, cancelled: closed || requestRevision !== revision });
122850
- return;
122851
- }
122852
123273
  await wallet.setPrivacyEnabled(desired);
122853
123274
  markDone(diag, "wallet.privacy", startedAt, { changed: true });
122854
123275
  }).catch((error) => {
@@ -123602,8 +124023,8 @@ function pendingTransfers(transfers = []) {
123602
124023
  function pendingTransferIds(transfers = []) {
123603
124024
  return pendingTransfers(transfers).map((tx) => String(tx.id));
123604
124025
  }
123605
- function pendingIncomingWalletTransfers(transfers = []) {
123606
- return pendingTransfers(transfers).filter((tx) => isIncomingTransfer(tx) && isWalletTransfer(tx));
124026
+ function pendingIncomingTransfers(transfers = []) {
124027
+ return pendingTransfers(transfers).filter(isIncomingTransfer);
123607
124028
  }
123608
124029
  function uniqueIds(ids = []) {
123609
124030
  return [...new Set((Array.isArray(ids) ? ids : [ids]).map((id) => String(id || "")).filter(Boolean))];
@@ -123698,7 +124119,7 @@ function adaptiveClaimBatchSize(maxBatchSize, queueDepth) {
123698
124119
  }
123699
124120
  return 1;
123700
124121
  }
123701
- function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, transferStore, appState = null, deferBackgroundWork = null, backgroundSignal = null, diag }) {
124122
+ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, eagerCacheHistory = true, transferStore, appState = null, deferBackgroundWork = null, backgroundSignal = null, refreshBalance = null, diag }) {
123702
124123
  if (!transferStore || typeof transferStore.subscribe !== "function" || typeof transferStore.getSnapshot !== "function" || typeof transferStore.getCacheState !== "function" || typeof transferStore.setHistory !== "function") {
123703
124124
  throw new Error("createWalletTransferSession requires transferStore");
123704
124125
  }
@@ -123712,6 +124133,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123712
124133
  const initialTransfers = canUseInitialStore ? transferStore.getTransfers() : [];
123713
124134
  const initialHistoryTransfers = canUseInitialStore ? transferStore.getHistoryTransfers() : [];
123714
124135
  const initialTxReady = canUseInitialStore && (initialStoreSnapshot.ready || initialTransfers.length > 0 || initialHistoryTransfers.length > 0);
124136
+ const initialCacheReady = initialTxReady && (eagerCacheHistory === false || initialStoreSnapshot.serverEndReached === true && initialHistoryTransfers.length >= initialStoreSnapshot.historyCount);
123715
124137
  const listeners = new Set;
123716
124138
  let isTxLoading = false;
123717
124139
  let publishedSnapshot = null;
@@ -123762,7 +124184,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123762
124184
  const cacheHydrateKeyRef = { current: "" };
123763
124185
  const cacheHydratePromiseRef = { current: null };
123764
124186
  const fullCacheHydratePromiseRef = { current: null };
123765
- const cacheWriteRef = { current: { timer: null, cache: null, state: null } };
124187
+ const cacheWriteRef = { current: { timer: null, cache: null, state: null, fullHistory: false } };
123766
124188
  const cacheDirtyRef = { current: false };
123767
124189
  const lastFetchAtRef = { current: 0 };
123768
124190
  const claimBatchSize = Math.max(1, Number(pendingClaimBatchSize) || PENDING_TRANSFER_CLAIM_BATCH_SIZE);
@@ -124090,9 +124512,9 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124090
124512
  return false;
124091
124513
  }
124092
124514
  const cachedTransfers = filterTransfersForWallet(cached.transfers, walletPKRef.current);
124093
- const cachedPendingIncoming = pendingIncomingWalletTransfers(cachedTransfers);
124094
- const cachedPending = pendingTransfers(cachedTransfers);
124095
- const cachedPublishTransfers = cachedTransfers;
124515
+ const cachedPublishTransfers = !publish && historyTransfersRef.current.length ? mergeTransferPageForWallet(historyTransfersRef.current, cachedTransfers, walletPKRef.current, "append") : cachedTransfers;
124516
+ const cachedPendingIncoming = pendingIncomingTransfers(cachedPublishTransfers);
124517
+ const cachedPending = pendingTransfers(cachedPublishTransfers);
124096
124518
  rememberPendingTransferIds(cachedPending.map((tx) => tx.id));
124097
124519
  markDiag(diag, label, {
124098
124520
  elapsedMs: Date.now() - startedAt,
@@ -124231,9 +124653,11 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124231
124653
  pending.timer = null;
124232
124654
  const cache = pending.cache;
124233
124655
  const state = pending.state;
124656
+ const fullHistory = pending.fullHistory;
124234
124657
  pending.state = null;
124235
124658
  if (cache && state) {
124236
- writeCachedTransferState(cache, state);
124659
+ const write = fullHistory ? writeCachedTransferState : writeCachedTransferHeadState;
124660
+ write(cache, state);
124237
124661
  }
124238
124662
  };
124239
124663
  const queueWalletFetch = (read) => {
@@ -124338,7 +124762,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124338
124762
  return pendingQueryPromiseRef.current;
124339
124763
  };
124340
124764
  const pendingIncomingClaimCandidates = (sourceTransfers = []) => {
124341
- return pendingIncomingWalletTransfers(sourceTransfers).filter((tx) => {
124765
+ return pendingIncomingTransfers(sourceTransfers).filter((tx) => {
124342
124766
  const id = String(tx?.id || "");
124343
124767
  return id && !claimedTransferIdsRef.current.has(id);
124344
124768
  });
@@ -124554,6 +124978,9 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124554
124978
  batchClaimedIds.push(...staged);
124555
124979
  flushClaimed();
124556
124980
  const uniqueBatchClaimedIds = uniqueIds(batchClaimedIds);
124981
+ if (uniqueBatchClaimedIds.length && typeof refreshBalance === "function") {
124982
+ await refreshBalance();
124983
+ }
124557
124984
  const unclaimedIds = candidateIds.filter((id) => !uniqueBatchClaimedIds.includes(id) && !claimedTransferIdsRef.current.has(id));
124558
124985
  if (unclaimedIds.length) {
124559
124986
  const elapsedMs = Date.now() - startedAt;
@@ -125076,7 +125503,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125076
125503
  try {
125077
125504
  await yieldToUi();
125078
125505
  const pendingTransfers2 = await getPendingTransfersSnapshot();
125079
- const incomingTransfers = pendingIncomingWalletTransfers(pendingTransfers2);
125506
+ const incomingTransfers = pendingIncomingTransfers(pendingTransfers2);
125080
125507
  const claimCandidates = pendingIncomingClaimCandidates(incomingTransfers);
125081
125508
  if (!incomingTransfers.length) {
125082
125509
  markDone(diag, "wallet.pendingTxs.active", startedAt, { pending: 0, claimed: 0, reason });
@@ -125421,13 +125848,16 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125421
125848
  if (cacheWriteRef.current.timer) {
125422
125849
  clearTimeout(cacheWriteRef.current.timer);
125423
125850
  }
125424
- cacheWriteRef.current = { timer: null, cache: null, state: null };
125851
+ cacheWriteRef.current = { timer: null, cache: null, state: null, fullHistory: false };
125425
125852
  cacheDirtyRef.current = false;
125426
125853
  };
125427
125854
  const startCacheHydration = () => {
125428
125855
  if (!wallet || !localCache || !walletPKRef.current) {
125429
125856
  return;
125430
125857
  }
125858
+ if (initialCacheReady) {
125859
+ return;
125860
+ }
125431
125861
  const hydrateKey = `${localCache.id || "cache"}:${walletPKRef.current}`;
125432
125862
  if (cacheHydrateKeyRef.current === hydrateKey) {
125433
125863
  return;
@@ -125452,7 +125882,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125452
125882
  return;
125453
125883
  }
125454
125884
  const hydratedHead = applyCachedTransferState(cachedHead, { label: "wallet.provider.cache.hydrate", publish: true, startedAt: hydrateStartedAt });
125455
- if (cancelled)
125885
+ if (cancelled || !eagerCacheHistory)
125456
125886
  return;
125457
125887
  await loadFullTransferCache({ label: "wallet.provider.cache.fullHydrate", publish: !hydratedHead });
125458
125888
  }).catch((error) => {
@@ -125476,12 +125906,10 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125476
125906
  if (!cacheDirtyRef.current && !cacheWriteRef.current.timer) {
125477
125907
  return;
125478
125908
  }
125479
- if (historyTransfersRef.current.length < knownHistoryCountRef.current) {
125480
- return;
125481
- }
125482
125909
  cacheDirtyRef.current = false;
125483
125910
  cacheWriteRef.current.cache = localCache;
125484
125911
  cacheWriteRef.current.state = transferStore.getCacheState();
125912
+ cacheWriteRef.current.fullHistory = historyTransfersRef.current.length >= knownHistoryCountRef.current;
125485
125913
  if (cacheWriteRef.current.timer) {
125486
125914
  clearTimeout(cacheWriteRef.current.timer);
125487
125915
  }
@@ -125510,7 +125938,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125510
125938
  serverHistoryComplete: serverHistoryCompleteRef.current,
125511
125939
  hasMoreTxs: !historyComplete,
125512
125940
  hasPendingTxs,
125513
- hasPendingIncomingTxs: pendingIncomingWalletTransfers(transfersRef.current).length > 0,
125941
+ hasPendingIncomingTxs: pendingIncomingTransfers(transfersRef.current).length > 0,
125514
125942
  beginSdkActivity,
125515
125943
  getRecentTxs,
125516
125944
  claimPendingIncomingTransfers: claimPendingIncomingTransfers2,
@@ -125861,10 +126289,12 @@ function createWalletSession({
125861
126289
  pendingClaimBatchSize = null,
125862
126290
  activeTransferRefreshMs = null,
125863
126291
  eagerOutgoingStatusRefresh = false,
126292
+ eagerCacheHistory = true,
125864
126293
  sentTransferRefreshDelayMs = null,
125865
126294
  deferBackgroundWork = null,
125866
126295
  appState = null,
125867
126296
  transferStore,
126297
+ cachedBalance = null,
125868
126298
  ghostWallet = true,
125869
126299
  diag = null
125870
126300
  }) {
@@ -125882,6 +126312,7 @@ function createWalletSession({
125882
126312
  const txSubscriptions = createFieldSubscriptions(() => publishedSnapshot?.txValue);
125883
126313
  let polling = null;
125884
126314
  let readyDiag = null;
126315
+ let refreshWalletBalance = null;
125885
126316
  const transferSession = createWalletTransferSession({
125886
126317
  wallet,
125887
126318
  walletPK,
@@ -125889,10 +126320,12 @@ function createWalletSession({
125889
126320
  claimIncomingTransfers,
125890
126321
  pendingClaimBatchSize,
125891
126322
  eagerOutgoingStatusRefresh,
126323
+ eagerCacheHistory,
125892
126324
  transferStore,
125893
126325
  appState,
125894
126326
  deferBackgroundWork,
125895
126327
  backgroundSignal: backgroundAbort.signal,
126328
+ refreshBalance: () => refreshWalletBalance?.(),
125896
126329
  diag
125897
126330
  });
125898
126331
  const getTransferState = () => transferSession.getSnapshot();
@@ -125912,7 +126345,16 @@ function createWalletSession({
125912
126345
  listener();
125913
126346
  }
125914
126347
  };
125915
- const balance = createWalletBalance({ wallet, diag, onChange: publish });
126348
+ const balance = createWalletBalance({
126349
+ wallet,
126350
+ diag,
126351
+ initialDisplayBalance: cachedBalance,
126352
+ onBalance: (value) => {
126353
+ writeCachedWalletBalance(localCache, { walletPK, network, balance: value }).catch(() => {});
126354
+ },
126355
+ onChange: publish
126356
+ });
126357
+ refreshWalletBalance = balance.getBalance;
125916
126358
  const funding = createFundingAddress({ wallet, network, diag, onChange: publish });
125917
126359
  const updateWalletData = createWalletData({
125918
126360
  wallet,
@@ -125993,13 +126435,14 @@ function createWalletSession({
125993
126435
  wallet,
125994
126436
  network,
125995
126437
  balance: balanceState.balance,
126438
+ displayBalance: balanceState.displayBalance,
125996
126439
  satsBalance: balanceState.satsBalance,
125997
126440
  tokenBalances: balanceState.tokenBalances,
125998
126441
  fundingAddress: funding.getSnapshot(),
125999
126442
  balanceReady: balanceState.balanceReady,
126000
126443
  isBalanceLoading: balanceState.isBalanceLoading,
126001
126444
  isWalletDataReady: true,
126002
- isWalletDataLoaded: balanceState.balanceReady && currentTransferState.txReady,
126445
+ isWalletDataLoaded: balanceState.balanceReady || currentTransferState.txReady,
126003
126446
  refresh: claims.refresh,
126004
126447
  requestTransferDiscoveryHotWindow: polling.requestTransferDiscoveryHotWindow,
126005
126448
  claimDeposits: claims.claimDeposits,
@@ -126091,7 +126534,7 @@ function createWalletSession({
126091
126534
  };
126092
126535
  }
126093
126536
  var init_session4 = __esm(() => {
126094
- init_balance();
126537
+ init_balance2();
126095
126538
  init_claims();
126096
126539
  init_funding();
126097
126540
  init_lightning();
@@ -126100,6 +126543,7 @@ var init_session4 = __esm(() => {
126100
126543
  init_send2();
126101
126544
  init_transfers2();
126102
126545
  init_withdraw();
126546
+ init_localdata();
126103
126547
  });
126104
126548
 
126105
126549
  // ../../shared/wallet/transferstore.js
@@ -126308,6 +126752,7 @@ function createTransferStore() {
126308
126752
  transfers: state.historyTransfers,
126309
126753
  walletPK: state.walletPK,
126310
126754
  serverEndReached: state.serverEndReached,
126755
+ historyCount: state.historyCount,
126311
126756
  nextOffset: state.nextOffset,
126312
126757
  oldestTxMs: state.serverEndReached ? state.oldestVerifiedTxMs : null
126313
126758
  };
@@ -169791,9 +170236,13 @@ function peerRecordFromDoc(snap) {
169791
170236
  function peerRecordsFromSnapshot(snapshot) {
169792
170237
  return snapshot.docs.map(peerRecordFromDoc).filter(Boolean);
169793
170238
  }
169794
- function avatarPath(uid) {
170239
+ function avatarPath(uid, version8) {
169795
170240
  requireUid(uid);
169796
- return `${uid}/avatar.webp`;
170241
+ const avatarVersion = readAvatarVersion(version8);
170242
+ if (!avatarVersion) {
170243
+ throw new Error("avatar version required");
170244
+ }
170245
+ return `${uid}/avatars/${avatarVersion}.webp`;
169797
170246
  }
169798
170247
  function reportEvidencePath(reporter, targetUid, evidenceId) {
169799
170248
  if (!reporter || !targetUid || !evidenceId) {
@@ -169859,6 +170308,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169859
170308
  }
169860
170309
  let activeVaultSigner = null;
169861
170310
  const localSessionTerminations = new Set;
170311
+ const pendingAvatars = new Map;
169862
170312
  function pageTs(value) {
169863
170313
  if (value instanceof Timestamp2) {
169864
170314
  return value;
@@ -169941,7 +170391,12 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169941
170391
  "Content-Type": upload?.metadata?.contentType || "application/octet-stream"
169942
170392
  };
169943
170393
  if (typeof uploadSignedStorageBytes === "function") {
169944
- await uploadSignedStorageBytes(url, upload?.body, { ...signed, method, headers, metadata: upload?.metadata || {}, path: upload?.path || "" });
170394
+ await uploadSignedStorageBytes(url, upload?.body, { ...signed, method, headers, metadata: upload?.metadata || {}, path: upload?.path || "" }).catch((error) => {
170395
+ if (error?.status === 412 && (headers["X-Goog-If-Generation-Match"] === "0" || headers["x-goog-if-generation-match"] === "0")) {
170396
+ return true;
170397
+ }
170398
+ throw error;
170399
+ });
169945
170400
  return true;
169946
170401
  }
169947
170402
  const payload = typeof Blob !== "undefined" && upload?.body instanceof Blob ? upload.body : toBytes2(upload?.body, "upload bytes");
@@ -169950,6 +170405,9 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
169950
170405
  headers,
169951
170406
  body: payload
169952
170407
  });
170408
+ if (response.status === 412 && (headers["X-Goog-If-Generation-Match"] === "0" || headers["x-goog-if-generation-match"] === "0")) {
170409
+ return true;
170410
+ }
169953
170411
  if (!response.ok) {
169954
170412
  const error = new Error(`signed upload failed (${response.status || 0})`);
169955
170413
  error.status = response.status || 0;
@@ -170119,6 +170577,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170119
170577
  }, onError);
170120
170578
  }
170121
170579
  async function logout() {
170580
+ pendingAvatars.clear();
170122
170581
  await signOut(requireAuth());
170123
170582
  return true;
170124
170583
  }
@@ -170252,25 +170711,45 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170252
170711
  await callFunction("acceptAgreement");
170253
170712
  return true;
170254
170713
  }
170255
- async function writeProfileAvatar(uid, avatar) {
170714
+ async function setProfileAvatar(uid, data) {
170256
170715
  requireUid(uid);
170257
- await updateDoc(doc(db, "profiles", uid), { avatar });
170258
- return true;
170259
- }
170260
- async function uploadProfileAvatar(uid, data, { contentType = "image/webp" } = {}) {
170261
- const targetStorage = requireStorage2();
170262
- const path = avatarPath(uid);
170263
- const result = typeof uploadStorageBytes === "function" ? await uploadStorageBytes(targetStorage, path, data, { contentType }) : await uploadBytes(ref(targetStorage, path), typeof Blob !== "undefined" && data instanceof Blob ? data : toBytes2(data, "upload bytes"), { contentType });
170264
- const url = await getDownloadURL(ref(targetStorage, path));
170265
- return {
170266
- url,
170267
- generation: result?.metadata?.generation ?? result?.generation ?? null
170268
- };
170269
- }
170270
- async function deleteProfileAvatar(uid) {
170271
- const targetStorage = requireStorage2();
170272
- await deleteObject(ref(targetStorage, avatarPath(uid)));
170273
- return true;
170716
+ if (requireAuth().currentUser?.uid !== uid) {
170717
+ throw new Error("avatar account mismatch");
170718
+ }
170719
+ if (data == null) {
170720
+ pendingAvatars.delete(uid);
170721
+ const result = await callFunction("setAvatar", { body: null });
170722
+ if (result?.avatar != null) {
170723
+ throw new Error("avatar clear returned invalid state");
170724
+ }
170725
+ return { version: null };
170726
+ }
170727
+ const bytes = new Uint8Array(encryptedBytes(data, "avatar bytes"));
170728
+ const pending = { bytes, version: null, promise: null };
170729
+ const entries = pendingAvatars.get(uid) || [];
170730
+ pendingAvatars.set(uid, [...entries, pending]);
170731
+ pending.promise = callFunction("setAvatar", {
170732
+ body: cloudBytesBase64(bytes, "avatar bytes")
170733
+ }).then((result) => {
170734
+ const version8 = readAvatarVersion(result?.avatar);
170735
+ if (!version8) {
170736
+ throw new Error("avatar publish returned invalid version");
170737
+ }
170738
+ pending.version = version8;
170739
+ return version8;
170740
+ });
170741
+ try {
170742
+ return { version: await pending.promise };
170743
+ } catch (error) {
170744
+ const active = pendingAvatars.get(uid) || [];
170745
+ const remaining = active.filter((entry) => entry !== pending);
170746
+ if (remaining.length) {
170747
+ pendingAvatars.set(uid, remaining);
170748
+ } else {
170749
+ pendingAvatars.delete(uid);
170750
+ }
170751
+ throw error;
170752
+ }
170274
170753
  }
170275
170754
  async function writeProfileIdentity(identity) {
170276
170755
  if (!identity || typeof identity !== "object" || Array.isArray(identity)) {
@@ -170407,13 +170886,28 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170407
170886
  onUpdate?.(snap.exists() ? snap.data()?.active === true : false, { exists: snap.exists() });
170408
170887
  }, onError);
170409
170888
  }
170410
- async function readPeerAvatar(uid) {
170411
- return readStorageFile(avatarPath(uid));
170889
+ async function readPeerAvatar(uid, { version: version8 = null } = {}) {
170890
+ const avatarVersion = readAvatarVersion(version8);
170891
+ const entries = pendingAvatars.get(uid) || [];
170892
+ if (entries.length) {
170893
+ await Promise.allSettled(entries.map((entry) => entry.promise));
170894
+ const pending = entries.find((entry) => entry.version === avatarVersion);
170895
+ if (pending) {
170896
+ const active = pendingAvatars.get(uid) || [];
170897
+ const remaining = active.filter((entry) => entry !== pending);
170898
+ if (remaining.length) {
170899
+ pendingAvatars.set(uid, remaining);
170900
+ } else {
170901
+ pendingAvatars.delete(uid);
170902
+ }
170903
+ return new Uint8Array(pending.bytes);
170904
+ }
170905
+ }
170906
+ return readStorageFile(avatarPath(uid, avatarVersion));
170412
170907
  }
170413
170908
  async function peerAvatarUrl(uid, { version: version8 = null } = {}) {
170414
170909
  const targetStorage = requireStorage2();
170415
- const url = await getDownloadURL(ref(targetStorage, avatarPath(uid)));
170416
- return avatarUrlWithVersion(url, version8);
170910
+ return getDownloadURL(ref(targetStorage, avatarPath(uid, version8)));
170417
170911
  }
170418
170912
  function profileQueryField(field2, { network } = {}) {
170419
170913
  if (field2 === "walletPK") {
@@ -170464,20 +170958,12 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170464
170958
  const snapshot = await getDocs(roleQuery);
170465
170959
  return peerRecordsFromSnapshot(snapshot);
170466
170960
  }
170467
- async function uploadChatMedia(upload) {
170961
+ async function uploadMedia(upload) {
170468
170962
  const path = upload?.path;
170469
170963
  if (!path)
170470
170964
  throw new Error("media path required");
170471
- getChatMediaFileRef(path);
170472
- await uploadStorageFile(path, upload?.body, upload?.metadata || {});
170473
- return true;
170474
- }
170475
- async function uploadSharedMedia(upload) {
170476
- const path = upload?.path;
170477
- if (!path)
170478
- throw new Error("shared media path required");
170479
- const signed = await callFunction("reserveSharedMediaUpload", {
170480
- sharedId: upload?.sharedId,
170965
+ getMediaFileRef(path);
170966
+ const signed = await callFunction("reserveMediaUpload", {
170481
170967
  path,
170482
170968
  size: uploadByteSize(upload?.body),
170483
170969
  contentType: upload?.metadata?.contentType || "application/octet-stream"
@@ -170485,6 +170971,14 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170485
170971
  await uploadSignedStorageFile(upload, signed?.upload);
170486
170972
  return true;
170487
170973
  }
170974
+ async function uploadChatMedia(upload) {
170975
+ getChatMediaFileRef(upload?.path);
170976
+ return uploadMedia(upload);
170977
+ }
170978
+ async function uploadSharedMedia(upload) {
170979
+ getSharedMediaFileRef(upload?.path);
170980
+ return uploadMedia(upload);
170981
+ }
170488
170982
  async function readChatMedia(path) {
170489
170983
  if (!path)
170490
170984
  throw new Error("media path required");
@@ -170872,12 +171366,13 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170872
171366
  }
170873
171367
  };
170874
171368
  }
170875
- async function pushInbox(recipientUid, ping) {
171369
+ async function pushInbox(recipientUid, ping, options2 = {}) {
170876
171370
  requireUid(recipientUid);
170877
171371
  if (!ping)
170878
171372
  throw new Error("inbox ping required");
170879
171373
  await callFunction("push", {
170880
171374
  recipientUid,
171375
+ notify: options2?.notify !== false,
170881
171376
  ping: {
170882
171377
  v: ping.v,
170883
171378
  epk: ping.epk,
@@ -170916,7 +171411,9 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170916
171411
  payload.onCommitted?.({ chatId, messageId });
170917
171412
  let delivered = true;
170918
171413
  if (payload.inbox?.recipientUid && payload.inbox?.ping) {
170919
- delivered = await pushInbox(payload.inbox.recipientUid, payload.inbox.ping).then(() => true, () => false);
171414
+ delivered = await pushInbox(payload.inbox.recipientUid, payload.inbox.ping, {
171415
+ notify: payload.inbox.notify !== false
171416
+ }).then(() => true, () => false);
170920
171417
  }
170921
171418
  return { chatId, messageId, delivered };
170922
171419
  }
@@ -171169,18 +171666,19 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
171169
171666
  }
171170
171667
  async function banAdminUser(uid, feature = "chat") {
171171
171668
  requireUid(uid);
171172
- await setDoc(doc(db, "moderation", uid), { banned: { [feature]: { until: null } } }, { merge: true });
171173
171669
  if (feature === "avatar") {
171174
- await deleteProfileAvatar(uid).catch((error) => {
171175
- if (error?.code !== "storage/object-not-found") {
171176
- throw error;
171177
- }
171178
- });
171670
+ await callFunction("setAvatarModeration", { uid, banned: true });
171671
+ return true;
171179
171672
  }
171673
+ await setDoc(doc(db, "moderation", uid), { banned: { [feature]: { until: null } } }, { merge: true });
171180
171674
  return true;
171181
171675
  }
171182
171676
  async function unbanAdminUser(uid, feature = "chat") {
171183
171677
  requireUid(uid);
171678
+ if (feature === "avatar") {
171679
+ await callFunction("setAvatarModeration", { uid, banned: false });
171680
+ return true;
171681
+ }
171184
171682
  await setDoc(doc(db, "moderation", uid), { banned: { [feature]: deleteField() } }, { merge: true });
171185
171683
  return true;
171186
171684
  }
@@ -171218,9 +171716,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
171218
171716
  write: writeProfileIdentity
171219
171717
  },
171220
171718
  avatar: {
171221
- write: writeProfileAvatar,
171222
- upload: uploadProfileAvatar,
171223
- delete: deleteProfileAvatar
171719
+ set: setProfileAvatar
171224
171720
  }
171225
171721
  },
171226
171722
  private: {
@@ -173810,13 +174306,6 @@ function cleanMessage(value, limit2, label = "message") {
173810
174306
  throw new Error(`${label} must be ${limit2} characters or fewer`);
173811
174307
  return message;
173812
174308
  }
173813
- function readAvatarGeneration(value) {
173814
- const version8 = Number(value);
173815
- if (!Number.isSafeInteger(version8) || version8 <= 0) {
173816
- throw new Error("avatar upload did not return a valid generation");
173817
- }
173818
- return version8;
173819
- }
173820
174309
  function passkeyRows(linksList, passkeys, currentPasskeyId) {
173821
174310
  return [...linksList, ...passkeys].sort((left, right) => (right.createdAt || 0) - (left.createdAt || 0)).map((row) => ({
173822
174311
  ...row,
@@ -173904,23 +174393,17 @@ function createRuntimeProductActions({
173904
174393
  const user = await getUser();
173905
174394
  if (!data)
173906
174395
  throw new Error("avatar data required");
173907
- const result = await cloud.user.profile.avatar.upload(user.uid, data, {
173908
- contentType: options2.contentType || "image/webp"
173909
- });
173910
- const avatarVersion = readAvatarGeneration(result?.generation);
173911
- await cloud.user.profile.avatar.write(user.uid, avatarVersion);
173912
- await user.refetchAvatar?.({ version: avatarVersion, optimistic: true });
173913
- return { avatar: result?.url || null, avatarVersion };
174396
+ if (options2.contentType && options2.contentType !== "image/webp") {
174397
+ throw new Error("avatar content type must be image/webp");
174398
+ }
174399
+ const result = await cloud.user.profile.avatar.set(user.uid, data);
174400
+ await user.refetchAvatar?.({ version: result.version });
174401
+ const updated = await getUser();
174402
+ return { avatar: updated.avatar || null, avatarVersion: result.version };
173914
174403
  },
173915
174404
  async deleteAvatar() {
173916
174405
  const user = await getUser();
173917
- try {
173918
- await cloud.user.profile.avatar.delete(user.uid);
173919
- } catch (error) {
173920
- if (error?.code !== "storage/object-not-found")
173921
- throw error;
173922
- }
173923
- await cloud.user.profile.avatar.write(user.uid, null);
174406
+ await cloud.user.profile.avatar.set(user.uid, null);
173924
174407
  user.clearAvatar?.();
173925
174408
  return { deleted: true };
173926
174409
  }
@@ -175688,6 +176171,7 @@ class VeylHeadlessClient {
175688
176171
  this.userOwner.setNetwork(network);
175689
176172
  }
175690
176173
  });
176174
+ await opened.walletReady;
175691
176175
  this.userSnapshot = this.userOwner.getSnapshot();
175692
176176
  } catch (error) {
175693
176177
  if (opened)