@glyphteck/veyl 0.35.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -447,6 +447,34 @@ var init_legal = __esm(() => {
447
447
  };
448
448
  });
449
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
+
450
478
  // src/commands.js
451
479
  import { Buffer as Buffer2 } from "node:buffer";
452
480
  import { chmod, readFile, writeFile } from "node:fs/promises";
@@ -614,6 +642,7 @@ function cliHelpLines() {
614
642
  }
615
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;
616
644
  var init_commands = __esm(() => {
645
+ init_actioncopy();
617
646
  stringProp = Object.freeze({ type: "string" });
618
647
  numberProp = Object.freeze({ type: "number" });
619
648
  booleanProp = Object.freeze({ type: "boolean" });
@@ -690,12 +719,12 @@ var init_commands = __esm(() => {
690
719
  invokeCli: async (client, input, context) => client.account.acceptTerms({ ...input, termsVersion: await context.acceptTerms(input.termsVersion) })
691
720
  }),
692
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() }),
693
- 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() }),
694
- 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) }),
695
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) }),
696
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) }),
697
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() }),
698
- 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) }),
699
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() }),
700
729
  command({
701
730
  name: "profile_avatar_set",
@@ -721,8 +750,8 @@ var init_commands = __esm(() => {
721
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) }),
722
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) }),
723
752
  command({ name: "peers_blocked", group: "peers", apiName: "blocked", description: "List blocked peers.", usage: "peers blocked", cliInput: optionsOnly, invoke: (client) => client.peers.blocked() }),
724
- 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) }),
725
- 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) }),
726
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) }),
727
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) }),
728
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) }),
@@ -735,7 +764,7 @@ var init_commands = __esm(() => {
735
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) }),
736
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) }),
737
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) }),
738
- 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) }),
739
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) }),
740
769
  command({
741
770
  name: "chat_send_file",
@@ -820,12 +849,12 @@ var init_commands = __esm(() => {
820
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) }),
821
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) }),
822
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) }),
823
- 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) }),
824
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) }),
825
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) }),
826
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) }),
827
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) }),
828
- 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) }),
829
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) }),
830
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) }),
831
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) }),
@@ -980,7 +1009,7 @@ var package_default;
980
1009
  var init_package = __esm(() => {
981
1010
  package_default = {
982
1011
  name: "veyl",
983
- version: "0.35.0",
1012
+ version: "0.41.0",
984
1013
  private: true,
985
1014
  license: "Apache-2.0",
986
1015
  workspaces: {
@@ -104546,7 +104575,7 @@ var init_config = __esm(() => {
104546
104575
  LOCAL_PROFILE_CACHE_MAX_AGE_MS = 30 * DAY_MS;
104547
104576
  LOCAL_AVATAR_CACHE_MAX_BYTES = 128 * MIB_BYTES;
104548
104577
  LOCAL_AVATAR_CACHE_MAX_AGE_MS = 30 * DAY_MS;
104549
- AVATAR_IMAGE_MAX_BYTES = 8 * MIB_BYTES;
104578
+ AVATAR_IMAGE_MAX_BYTES = 256 * KIB_BYTES;
104550
104579
  CHAT_READY_DOWNLOAD_MAX_BYTES = 8 * MIB_BYTES;
104551
104580
  CHAT_MESSAGE_FILE_CACHE_MAX_BYTES = 64 * MIB_BYTES;
104552
104581
  CHAT_UNSAVED_TTL_MS = CHAT_UNSAVED_TTL_DAYS * DAY_MS;
@@ -106991,18 +107020,13 @@ var init_values = __esm(() => {
106991
107020
 
106992
107021
  // ../../shared/avatar.js
106993
107022
  function readAvatarVersion(value) {
106994
- if (value == null || value === "" || typeof value !== "number" && typeof value !== "string") {
106995
- return null;
106996
- }
106997
- const version = Number(value);
106998
- 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;
106999
107025
  }
107000
- function avatarUrlWithVersion(url, version) {
107001
- if (!url)
107002
- return null;
107003
- return version == null ? url : `${url}${url.includes("?") ? "&" : "?"}v=${encodeURIComponent(String(version))}`;
107004
- }
107005
- var init_avatar = () => {};
107026
+ var AVATAR_VERSION_RE;
107027
+ var init_avatar = __esm(() => {
107028
+ AVATAR_VERSION_RE = /^[0-9a-f]{64}$/;
107029
+ });
107006
107030
 
107007
107031
  // ../../shared/moderation.js
107008
107032
  function banUntilMs(ban) {
@@ -107280,11 +107304,16 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107280
107304
  markDiag(diag, "user.avatar.fetch.start", { force: !!force, persist: !!persist, hasVersion: avatarVersion != null });
107281
107305
  try {
107282
107306
  const promise = (async () => {
107283
- const nextAvatar = await cloud.peer.avatar.url(uid, { version: avatarVersion });
107284
- if (persist && avatarVersion != null) {
107285
- 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 {}
107286
107315
  }
107287
- return nextAvatar;
107316
+ return cloud.peer.avatar.url(uid, { version: avatarVersion });
107288
107317
  })().then((nextAvatar) => {
107289
107318
  markDone(diag, "user.avatar.fetch", startedAt, { found: !!nextAvatar, hasVersion: avatarVersion != null });
107290
107319
  if (!nextAvatar || avatarFetch.uid !== uid || avatarFetch.key !== key) {
@@ -107624,10 +107653,12 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
107624
107653
  }
107625
107654
  function refetchAvatar(options = {}) {
107626
107655
  const requestedVersion = readAvatarVersion(options?.version);
107627
- const optimistic = options?.optimistic === true;
107628
- const nextVersion = requestedVersion ?? (optimistic && state.avatarVersion != null ? state.avatarVersion + 1 : state.avatarVersion);
107629
- const persist = options?.persist ?? !(optimistic && requestedVersion == null);
107630
- 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
+ });
107631
107662
  }
107632
107663
  function setNetwork(nextNetwork) {
107633
107664
  if (activeNetwork === nextNetwork)
@@ -110045,7 +110076,7 @@ function sameSigningKeys(left, right) {
110045
110076
  function sameChatShape(a, b) {
110046
110077
  if (!a || !b)
110047
110078
  return a === b;
110048
- return a.id === b.id && a.protocol === b.protocol && a.linkId === b.linkId && a.entryId === b.entryId && a.peerChatPK === b.peerChatPK && a.peerUid === b.peerUid && sameSigningKeys(a.signingKeysByChatKey, b.signingKeysByChatKey) && a.ts === b.ts && a.startMs === b.startMs && a.unseen === b.unseen && a.settings?.retention === b.settings?.retention && sameChatPreview(a.preview, b.preview);
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);
110049
110080
  }
110050
110081
  function sameChats(prev, next) {
110051
110082
  if (prev.length !== next.length)
@@ -110404,8 +110435,10 @@ function emptyPayload() {
110404
110435
  chatsById: {},
110405
110436
  profilesByUid: {},
110406
110437
  mediaByKey: {},
110438
+ walletBalance: null,
110407
110439
  resumeRoute: null,
110408
- lastCameraFacing: null
110440
+ lastCameraFacing: null,
110441
+ cloaked: false
110409
110442
  };
110410
110443
  }
110411
110444
  function isObject3(value) {
@@ -110466,8 +110499,26 @@ function normalizePayload(value) {
110466
110499
  chatsById: isObject3(input.chatsById) ? input.chatsById : {},
110467
110500
  profilesByUid: isObject3(input.profilesByUid) ? input.profilesByUid : {},
110468
110501
  mediaByKey: isObject3(input.mediaByKey) ? input.mediaByKey : {},
110502
+ walletBalance: cleanWalletBalance(input.walletBalance),
110469
110503
  resumeRoute: cleanResumeRoute(input.resumeRoute),
110470
- 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
110471
110522
  };
110472
110523
  }
110473
110524
  function cleanResumeRoute(route) {
@@ -110913,8 +110964,8 @@ function stateFromPayload(payload, { chatId, selfChatPublicKey, peerChatPublicKe
110913
110964
  if (!cleanOlder.length && !cleanLive.length) {
110914
110965
  return { rejectReason: "empty-message-cache" };
110915
110966
  }
110916
- const historyStartReached = payload.historyStartReached === true && payload.truncated !== true;
110917
- 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;
110918
110969
  const partialOlderThan = partial ? markerFromMessage(cleanOlder[0]) : null;
110919
110970
  return {
110920
110971
  historyMessages: cleanOlder,
@@ -111059,7 +111110,7 @@ function writeCachedMessageState(cache, { stateVersion = null, chatId, selfChatP
111059
111110
  signingKeysVersion: cleanText(signingKeysVersion),
111060
111111
  savedAt: Date.now(),
111061
111112
  hasOlder: nextHasOlder === true || compacted.truncated,
111062
- historyStartReached: nextHistoryStartReached === true && compacted.truncated !== true,
111113
+ historyStartReached: nextHistoryStartReached === true,
111063
111114
  olderThan: serializeMarker(storedOlderThan),
111064
111115
  olderLoaded: nextOlderLoaded === true || compacted.historyMessages.length > 0,
111065
111116
  latestServerPage: batchFromLive(compacted.latestMessages, expiredKeys, deletedKeys, nextServerBatch),
@@ -111318,6 +111369,36 @@ var init_chats2 = __esm(() => {
111318
111369
  "use client";
111319
111370
  });
111320
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
+
111321
111402
  // ../../shared/cache/localdata/profiles.js
111322
111403
  function isUsableProfile(profile, now = Date.now()) {
111323
111404
  if (!profile?.uid || !profile.walletPK && !profile.chatPK) {
@@ -111558,24 +111639,26 @@ async function readCachedTransferHeadState(cache, { walletPK = null } = {}) {
111558
111639
  const stored = cache ? await cache.readPart(TX_HEAD_PART).catch(() => null) : null;
111559
111640
  return stateFromPayload2(stored, { walletPK, source: "head" });
111560
111641
  }
111561
- function writeCachedTransferState(cache, { transfers, walletPK = null, serverEndReached = false, nextOffset = null, oldestTxMs = null } = {}) {
111562
- if (!cache || !Array.isArray(transfers)) {
111563
- 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;
111564
111645
  }
111565
111646
  const nextWalletPK = lowerText(walletPK);
111566
111647
  if (!nextWalletPK) {
111567
- return Promise.resolve(false);
111648
+ return null;
111568
111649
  }
111569
111650
  const visible = [...transfers].filter((tx) => tx?.id && isVisibleTransfer(tx) && transferBelongsToWallet(tx, nextWalletPK)).sort((a, b) => txCreatedMs(b) - txCreatedMs(a));
111570
111651
  const sorted2 = visible.slice(0, Math.max(1, WALLET_TRANSFER_CACHE_LIMIT)).map(jsonClean);
111571
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;
111572
111655
  const fullPayload = {
111573
111656
  version: CACHE_VERSION,
111574
111657
  walletPK: nextWalletPK,
111575
- serverEndReached: serverEndReached === true && !capped,
111576
- historyCount: sorted2.length,
111658
+ serverEndReached: complete,
111659
+ historyCount: knownHistoryCount,
111577
111660
  nextOffset: Number.isFinite(nextOffset) ? nextOffset : sorted2.length,
111578
- oldestTxMs: serverEndReached === true && !capped && Number.isFinite(oldestTxMs) ? oldestTxMs : null,
111661
+ oldestTxMs: complete && Number.isFinite(oldestTxMs) ? oldestTxMs : null,
111579
111662
  savedAt: Date.now(),
111580
111663
  transfers: sorted2
111581
111664
  };
@@ -111584,7 +111667,24 @@ function writeCachedTransferState(cache, { transfers, walletPK = null, serverEnd
111584
111667
  ...fullPayload,
111585
111668
  transfers: headTransfers
111586
111669
  };
111587
- 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));
111588
111688
  }
111589
111689
  var TX_PART = "wallet-transfers", TX_HEAD_PART = "wallet-transfers-head", CACHE_VERSION = 2, CACHE_HEAD_LIMIT;
111590
111690
  var init_transfers = __esm(() => {
@@ -112060,6 +112160,7 @@ var init_vault2 = __esm(() => {
112060
112160
  // ../../shared/cache/localdata.js
112061
112161
  var init_localdata = __esm(() => {
112062
112162
  init_chats2();
112163
+ init_balance();
112063
112164
  init_media();
112064
112165
  init_messages2();
112065
112166
  init_profiles();
@@ -112137,7 +112238,8 @@ function makeOwnChatEntry(pair, fields = {}) {
112137
112238
  preview: fields.preview || null,
112138
112239
  saved: fields.saved || null,
112139
112240
  readMs: Number.isFinite(fields.readMs) ? fields.readMs : null,
112140
- startMs: Number.isFinite(fields.startMs) ? fields.startMs : null
112241
+ startMs: Number.isFinite(fields.startMs) ? fields.startMs : null,
112242
+ signerShared: fields.signerShared === true
112141
112243
  };
112142
112244
  }
112143
112245
  var CHAT_ENTRY_VERSION = 1;
@@ -112902,7 +113004,25 @@ async function setChatRead(cloud, uid, chatPrivKey, chatId, readMs, options = {}
112902
113004
  body: await sealOwnChatEntry(chatPrivKey, entryId, {
112903
113005
  ...entry,
112904
113006
  readMs: Math.max(currentReadMs, nextReadMs),
112905
- ...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
112906
113026
  })
112907
113027
  });
112908
113028
  return true;
@@ -112945,7 +113065,8 @@ async function ownEntryWrite(cloud, uid, chatPrivKey, pair, fields = {}) {
112945
113065
  preview: fields.preview || existing?.preview,
112946
113066
  saved: existing?.saved || null,
112947
113067
  readMs: existing?.readMs,
112948
- startMs: existing?.startMs
113068
+ startMs: existing?.startMs,
113069
+ signerShared: fields.signerShared === true || existing?.signerShared === true
112949
113070
  });
112950
113071
  const tsMs = timestampMs(fields.ts, null);
112951
113072
  return {
@@ -113040,10 +113161,22 @@ async function sendMsg(cloud, senderPubkey, senderPrivkey, receiverChatPK, messa
113040
113161
  messageId,
113041
113162
  message: msgData,
113042
113163
  ownerEntry,
113043
- inbox: ping ? { recipientUid: recipientProfile?.uid, ping } : null,
113164
+ inbox: ping ? { recipientUid: recipientProfile?.uid, ping, notify: options?.notify !== false } : null,
113044
113165
  onCommitted: typeof options?.onCommitted === "function" ? (committed) => options.onCommitted({ ...committed, message: sentMessage, preview: preview2 }) : undefined
113045
113166
  });
113046
- return { chatId, msgId: messageId, cid: head.cid, message: sentMessage, preview: preview2, ...write?.skipped ? { skipped: true } : {} };
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
+ };
113047
113180
  });
113048
113181
  }
113049
113182
  async function sendReadReceipt(cloud, senderPubkey, senderPrivkey, receiverChatPK, target, options = {}) {
@@ -113052,7 +113185,13 @@ async function sendReadReceipt(cloud, senderPubkey, senderPrivkey, receiverChatP
113052
113185
  cid: makeCid(),
113053
113186
  s: senderPubkey
113054
113187
  };
113055
- 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
+ });
113056
113195
  }
113057
113196
  async function sendReaction(cloud, senderPubkey, senderPrivkey, receiverChatPK, target, emoji, options = {}) {
113058
113197
  const reaction = {
@@ -113669,7 +113808,7 @@ function messageExpiryGroups(messages, selfChatPublicKey, seenAt) {
113669
113808
  { messages: day, expiresAt: seenAt + AFTER_SEEN_MS }
113670
113809
  ].filter((group) => group.messages.length);
113671
113810
  }
113672
- 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 }) {
113673
113812
  const scheduleRead = (chatId, message, previewMs, sendReceipt, expiryMessages = []) => {
113674
113813
  scheduleReadWrite({
113675
113814
  pendingRead: pendingReadRef.current,
@@ -113680,24 +113819,37 @@ function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localC
113680
113819
  expiryMessages: sendReceipt ? expiryMessages : [],
113681
113820
  interval: readWriteInterval,
113682
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
+ }
113683
113840
  const writes = [
113684
113841
  setChatRead(cloud, uid, chatPrivateKey, chatId, pending.previewMs, {
113685
- 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
113686
113844
  })
113687
113845
  ];
113688
113846
  if (pending.receipt) {
113689
- writes.push(sendReadReceipt(cloud, chatPK, chatPrivateKey, pending.receipt.peerChatPK, pending.receipt.target, {
113690
- chatId,
113691
- retention: getChatRetention(chatId),
113692
- senderUid: uid
113693
- }));
113694
113847
  for (const group of messageExpiryGroups(pending.expiryMessages, chatPK, pending.expirySeenAt)) {
113695
113848
  writes.push(setMessageExpiry(cloud, chatId, group.messages, group.expiresAt));
113696
113849
  }
113697
113850
  }
113698
113851
  const results = await Promise.allSettled(writes);
113699
113852
  const ownerError = results[0]?.status === "rejected" ? results[0].reason : null;
113700
- const receiptError = pending.receipt && results[1]?.status === "rejected" ? results[1].reason : null;
113701
113853
  if (ownerError) {
113702
113854
  markError(diag, "chat.read.state.write", Date.now(), ownerError);
113703
113855
  }
@@ -113705,7 +113857,7 @@ function createChatSeen({ cloud, uid, chatBanned, chatPK, chatPrivateKey, localC
113705
113857
  readCacheRef.current.delete(chatId);
113706
113858
  markError(diag, "chat.read.receipt.write", Date.now(), receiptError);
113707
113859
  }
113708
- for (const result of results.slice(pending.receipt ? 2 : 1)) {
113860
+ for (const result of results.slice(1)) {
113709
113861
  if (result?.status === "rejected") {
113710
113862
  markError(diag, "chat.message.expiry.write", Date.now(), result.reason);
113711
113863
  }
@@ -115756,7 +115908,11 @@ function readableEntryWindow(entries, pageSize, chatPK, peerChatPK, options = {}
115756
115908
  const limitCount = positiveInt(pageSize, MSG_BATCH_SIZE);
115757
115909
  const projectedEntries = readableProjectedEntries(source, chatPK, peerChatPK, options);
115758
115910
  if (!projectedEntries.length) {
115759
- return { count: 0, entries: [], start: 0 };
115911
+ return {
115912
+ count: 0,
115913
+ entries: source.filter((entry) => isProjectionSupportMsg(entry?.message)),
115914
+ start: 0
115915
+ };
115760
115916
  }
115761
115917
  const indexes = projectedEntries.map((entry) => entry.index);
115762
115918
  const projectedByIndex = new Map(projectedEntries.map((entry) => [entry.index, entry.message]));
@@ -118116,6 +118272,7 @@ function chatFromPing(ping, entryId, existing, preview2, userChatPK, ms, options
118116
118272
  saved: existing?.saved || null,
118117
118273
  readMs,
118118
118274
  startMs,
118275
+ signerShared: existing?.signerShared === true,
118119
118276
  preview: visiblePreview,
118120
118277
  ts,
118121
118278
  unseen: visiblePreview ? isChatUnseenForUser({ preview: visiblePreview, readMs }, userChatPK) : false
@@ -118174,7 +118331,8 @@ async function savePing(cloud, uid, userChatPK, userPrivKey, ping, options = {})
118174
118331
  settings: chatSettingsFromPreview(existing?.settings, preview2),
118175
118332
  preview: preview2 || existing?.preview,
118176
118333
  readMs: existing?.readMs,
118177
- 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
118178
118336
  });
118179
118337
  const body = await sealOwnChatEntry(userPrivKey, entryId, entry);
118180
118338
  const record = { body };
@@ -118184,14 +118342,16 @@ async function savePing(cloud, uid, userChatPK, userPrivKey, ping, options = {})
118184
118342
  await cloud.user.chats.write(uid, entryId, record);
118185
118343
  return true;
118186
118344
  }
118187
- function chatWithReadPreview(existing, preview2, userChatPK) {
118188
- if (!existing?.id || !preview2) {
118345
+ function chatWithReadPreview(existing, preview2, userChatPK, fields = {}) {
118346
+ if (!existing?.id) {
118189
118347
  return null;
118190
118348
  }
118191
118349
  return {
118192
118350
  ...existing,
118193
- preview: preview2,
118194
- 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
118195
118355
  };
118196
118356
  }
118197
118357
  function patchReadPingPreview(existing, receiptPreview, userChatPK) {
@@ -118272,6 +118432,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
118272
118432
  let current = chatsById.get(group.chatId) || null;
118273
118433
  let wrote = false;
118274
118434
  let handled = false;
118435
+ const identityPing = group.message?.ping || group.read?.ping || group.delete?.ping;
118436
+ const peerUid2 = await resolvePingUid(cloud, identityPing?.payload);
118275
118437
  if (group.message) {
118276
118438
  const item = group.message;
118277
118439
  const entryId = ownChatEntryId(userPrivKey, item.chatId);
@@ -118292,7 +118454,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
118292
118454
  signingKeysByChatKey,
118293
118455
  preview: preview2,
118294
118456
  startPreview,
118295
- tsMs: item.ms
118457
+ tsMs: item.ms,
118458
+ peerUid: peerUid2
118296
118459
  });
118297
118460
  }
118298
118461
  if (group.read) {
@@ -118302,22 +118465,24 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
118302
118465
  const signingKeysByChatKey = pingSigningKeys(item.ping, existing);
118303
118466
  const receiptPreview = await readPingMsg(cloud, userChatPK, userPrivKey, item.ping, signingKeysByChatKey);
118304
118467
  const openedPreview = patchReadPingPreview(existing, receiptPreview, userChatPK);
118305
- const chat = chatWithReadPreview(current, openedPreview, userChatPK);
118468
+ const chat = chatWithReadPreview(current || existing, openedPreview, userChatPK, {
118469
+ signingKeysByChatKey,
118470
+ peerUid: peerUid2
118471
+ });
118306
118472
  handled = true;
118307
- if (openedPreview) {
118308
- if (chat) {
118309
- current = chat;
118310
- chatsById.set(chat.id, chat);
118311
- options.onPingChat?.(chat);
118312
- }
118313
- const writeExisting = chat || existing;
118314
- wrote = await savePing(cloud, uid, userChatPK, userPrivKey, item.ping, {
118315
- existing: writeExisting,
118316
- signingKeysByChatKey,
118317
- preview: openedPreview,
118318
- writeTs: false
118319
- }) || wrote;
118473
+ if (chat) {
118474
+ current = chat;
118475
+ chatsById.set(chat.id, chat);
118476
+ options.onPingChat?.(chat);
118320
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;
118321
118486
  }
118322
118487
  if (group.delete) {
118323
118488
  const item = group.delete;
@@ -118338,7 +118503,8 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
118338
118503
  wrote = await savePing(cloud, uid, userChatPK, userPrivKey, item.ping, {
118339
118504
  existing: writeExisting,
118340
118505
  signingKeysByChatKey,
118341
- preview: deletedPreview
118506
+ preview: deletedPreview,
118507
+ peerUid: peerUid2
118342
118508
  }) || wrote;
118343
118509
  }
118344
118510
  }
@@ -118744,6 +118910,7 @@ async function decryptChatEntry(entryRecord, userChatPK, userPrivKey) {
118744
118910
  saved: entry.saved || null,
118745
118911
  readMs,
118746
118912
  startMs,
118913
+ signerShared: entry.signerShared === true,
118747
118914
  preview: visiblePreview,
118748
118915
  ts,
118749
118916
  unseen: visiblePreview ? isChatUnseenForUser({ preview: visiblePreview, readMs }, userChatPK) : false
@@ -119530,6 +119697,7 @@ function createChatList({
119530
119697
  chatExists: !!peerChat?.id || hasServerChatForPeer(peerChatPK),
119531
119698
  receiverUid: peerChat?.peerUid || "",
119532
119699
  peerSigningPublicKey: peerChat?.signingKeysByChatKey?.[peerChatPK] || "",
119700
+ signerShared: peerChat?.signerShared === true,
119533
119701
  ownEntry: peerChat?.entryId ? peerChat : null
119534
119702
  };
119535
119703
  };
@@ -120227,6 +120395,7 @@ function createChatSession({
120227
120395
  readCacheRef,
120228
120396
  readWriteInterval,
120229
120397
  getChatRetention,
120398
+ sendOptionsForPeer,
120230
120399
  setChats,
120231
120400
  diag
120232
120401
  });
@@ -121371,7 +121540,7 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
121371
121540
  }
121372
121541
  if (avatarVersion != null && typeof cloud.peer.avatar.read === "function") {
121373
121542
  try {
121374
- const bytes = await cloud.peer.avatar.read(uid);
121543
+ const bytes = await cloud.peer.avatar.read(uid, { version: avatarVersion });
121375
121544
  const cachedSource = await writeCachedAvatar(uid, avatarVersion, bytes);
121376
121545
  if (cachedSource) {
121377
121546
  return cachedSource;
@@ -121908,6 +122077,10 @@ function getSatsBalance(result) {
121908
122077
  }
121909
122078
  return satsBalance;
121910
122079
  }
122080
+ function displayBalanceValue(value) {
122081
+ const balance = Number(value);
122082
+ return Number.isSafeInteger(balance) && balance >= 0 ? balance : null;
122083
+ }
121911
122084
  function samePlainObject(a, b) {
121912
122085
  if (a === b) {
121913
122086
  return true;
@@ -121936,9 +122109,10 @@ function sameTokenBalances(a, b) {
121936
122109
  }
121937
122110
  return true;
121938
122111
  }
121939
- function createWalletBalance({ wallet, diag, onChange = null }) {
122112
+ function createWalletBalance({ wallet, diag, initialDisplayBalance = null, onBalance = null, onChange = null }) {
121940
122113
  let snapshot = {
121941
122114
  balance: null,
122115
+ displayBalance: displayBalanceValue(initialDisplayBalance),
121942
122116
  satsBalance: null,
121943
122117
  tokenBalances: new Map,
121944
122118
  balanceReady: false,
@@ -121948,10 +122122,28 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121948
122122
  snapshot = { ...snapshot, ...changes };
121949
122123
  onChange?.();
121950
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
+ };
121951
122135
  const setBalance = (nextBalance) => {
121952
122136
  const value = typeof nextBalance === "function" ? nextBalance(snapshot.balance) : nextBalance;
122137
+ const changes = {};
121953
122138
  if (!Object.is(snapshot.balance, value)) {
121954
- 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);
121955
122147
  }
121956
122148
  };
121957
122149
  const setTokenBalances = (nextTokenBalances) => {
@@ -121960,12 +122152,13 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121960
122152
  publish({ tokenBalances: new Map(source) });
121961
122153
  }
121962
122154
  };
121963
- const setBalanceResult = (result) => {
122155
+ const setBalanceResult = (result, { ready = false } = {}) => {
121964
122156
  const changes = {};
121965
122157
  const nextBalance = getBalanceValue(result);
121966
122158
  if (nextBalance != null && !Object.is(snapshot.balance, nextBalance)) {
121967
122159
  changes.balance = nextBalance;
121968
122160
  }
122161
+ const nextDisplayBalance = publishDisplayBalance(changes, nextBalance, true);
121969
122162
  const nextSatsBalance = getSatsBalance(result);
121970
122163
  if (nextSatsBalance && !samePlainObject(snapshot.satsBalance, nextSatsBalance)) {
121971
122164
  changes.satsBalance = nextSatsBalance;
@@ -121974,9 +122167,15 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121974
122167
  if (nextTokenBalances && !sameTokenBalances(snapshot.tokenBalances, nextTokenBalances)) {
121975
122168
  changes.tokenBalances = new Map(nextTokenBalances);
121976
122169
  }
122170
+ if (ready && !snapshot.balanceReady) {
122171
+ changes.balanceReady = true;
122172
+ }
121977
122173
  if (Object.keys(changes).length) {
121978
122174
  publish(changes);
121979
122175
  }
122176
+ if (nextDisplayBalance != null) {
122177
+ onBalance?.(nextDisplayBalance);
122178
+ }
121980
122179
  };
121981
122180
  const setSatsBalanceResult = (nextSatsBalance) => {
121982
122181
  if (!nextSatsBalance || typeof nextSatsBalance !== "object") {
@@ -121989,9 +122188,13 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
121989
122188
  if (nextSatsBalance.available != null && !Object.is(snapshot.balance, nextSatsBalance.available)) {
121990
122189
  changes.balance = nextSatsBalance.available;
121991
122190
  }
122191
+ const nextDisplayBalance = publishDisplayBalance(changes, nextSatsBalance.available);
121992
122192
  if (Object.keys(changes).length) {
121993
122193
  publish(changes);
121994
122194
  }
122195
+ if (nextDisplayBalance != null) {
122196
+ onBalance?.(nextDisplayBalance);
122197
+ }
121995
122198
  };
121996
122199
  const getBalance = async () => {
121997
122200
  if (!wallet) {
@@ -122005,7 +122208,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
122005
122208
  }
122006
122209
  try {
122007
122210
  const result = await wallet.getBalance();
122008
- setBalanceResult(result);
122211
+ setBalanceResult(result, { ready: true });
122009
122212
  markDone(diag, "wallet.balance", startedAt, { hasBalance: getBalanceValue(result) != null, hasSatsBalance: !!getSatsBalance(result), hasTokenBalances: !!getTokenBalances(result) });
122010
122213
  } catch (error) {
122011
122214
  markError(diag, "wallet.balance", startedAt, error);
@@ -122025,6 +122228,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
122025
122228
  const reset = () => {
122026
122229
  snapshot = {
122027
122230
  balance: null,
122231
+ displayBalance: null,
122028
122232
  satsBalance: null,
122029
122233
  tokenBalances: new Map,
122030
122234
  balanceReady: false,
@@ -122041,7 +122245,7 @@ function createWalletBalance({ wallet, diag, onChange = null }) {
122041
122245
  setSatsBalanceResult
122042
122246
  };
122043
122247
  }
122044
- var init_balance = () => {};
122248
+ var init_balance2 = () => {};
122045
122249
 
122046
122250
  // ../../shared/wallet/deposits.js
122047
122251
  async function getClaimableDepositUtxos(wallet, options = {}) {
@@ -123819,8 +124023,8 @@ function pendingTransfers(transfers = []) {
123819
124023
  function pendingTransferIds(transfers = []) {
123820
124024
  return pendingTransfers(transfers).map((tx) => String(tx.id));
123821
124025
  }
123822
- function pendingIncomingWalletTransfers(transfers = []) {
123823
- return pendingTransfers(transfers).filter((tx) => isIncomingTransfer(tx) && isWalletTransfer(tx));
124026
+ function pendingIncomingTransfers(transfers = []) {
124027
+ return pendingTransfers(transfers).filter(isIncomingTransfer);
123824
124028
  }
123825
124029
  function uniqueIds(ids = []) {
123826
124030
  return [...new Set((Array.isArray(ids) ? ids : [ids]).map((id) => String(id || "")).filter(Boolean))];
@@ -123915,7 +124119,7 @@ function adaptiveClaimBatchSize(maxBatchSize, queueDepth) {
123915
124119
  }
123916
124120
  return 1;
123917
124121
  }
123918
- function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomingTransfers, pendingClaimBatchSize = null, eagerOutgoingStatusRefresh = false, eagerCacheHistory = true, 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 }) {
123919
124123
  if (!transferStore || typeof transferStore.subscribe !== "function" || typeof transferStore.getSnapshot !== "function" || typeof transferStore.getCacheState !== "function" || typeof transferStore.setHistory !== "function") {
123920
124124
  throw new Error("createWalletTransferSession requires transferStore");
123921
124125
  }
@@ -123980,7 +124184,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
123980
124184
  const cacheHydrateKeyRef = { current: "" };
123981
124185
  const cacheHydratePromiseRef = { current: null };
123982
124186
  const fullCacheHydratePromiseRef = { current: null };
123983
- const cacheWriteRef = { current: { timer: null, cache: null, state: null } };
124187
+ const cacheWriteRef = { current: { timer: null, cache: null, state: null, fullHistory: false } };
123984
124188
  const cacheDirtyRef = { current: false };
123985
124189
  const lastFetchAtRef = { current: 0 };
123986
124190
  const claimBatchSize = Math.max(1, Number(pendingClaimBatchSize) || PENDING_TRANSFER_CLAIM_BATCH_SIZE);
@@ -124308,9 +124512,9 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124308
124512
  return false;
124309
124513
  }
124310
124514
  const cachedTransfers = filterTransfersForWallet(cached.transfers, walletPKRef.current);
124311
- const cachedPendingIncoming = pendingIncomingWalletTransfers(cachedTransfers);
124312
- const cachedPending = pendingTransfers(cachedTransfers);
124313
- 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);
124314
124518
  rememberPendingTransferIds(cachedPending.map((tx) => tx.id));
124315
124519
  markDiag(diag, label, {
124316
124520
  elapsedMs: Date.now() - startedAt,
@@ -124449,9 +124653,11 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124449
124653
  pending.timer = null;
124450
124654
  const cache = pending.cache;
124451
124655
  const state = pending.state;
124656
+ const fullHistory = pending.fullHistory;
124452
124657
  pending.state = null;
124453
124658
  if (cache && state) {
124454
- writeCachedTransferState(cache, state);
124659
+ const write = fullHistory ? writeCachedTransferState : writeCachedTransferHeadState;
124660
+ write(cache, state);
124455
124661
  }
124456
124662
  };
124457
124663
  const queueWalletFetch = (read) => {
@@ -124556,7 +124762,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124556
124762
  return pendingQueryPromiseRef.current;
124557
124763
  };
124558
124764
  const pendingIncomingClaimCandidates = (sourceTransfers = []) => {
124559
- return pendingIncomingWalletTransfers(sourceTransfers).filter((tx) => {
124765
+ return pendingIncomingTransfers(sourceTransfers).filter((tx) => {
124560
124766
  const id = String(tx?.id || "");
124561
124767
  return id && !claimedTransferIdsRef.current.has(id);
124562
124768
  });
@@ -124772,6 +124978,9 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
124772
124978
  batchClaimedIds.push(...staged);
124773
124979
  flushClaimed();
124774
124980
  const uniqueBatchClaimedIds = uniqueIds(batchClaimedIds);
124981
+ if (uniqueBatchClaimedIds.length && typeof refreshBalance === "function") {
124982
+ await refreshBalance();
124983
+ }
124775
124984
  const unclaimedIds = candidateIds.filter((id) => !uniqueBatchClaimedIds.includes(id) && !claimedTransferIdsRef.current.has(id));
124776
124985
  if (unclaimedIds.length) {
124777
124986
  const elapsedMs = Date.now() - startedAt;
@@ -125294,7 +125503,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125294
125503
  try {
125295
125504
  await yieldToUi();
125296
125505
  const pendingTransfers2 = await getPendingTransfersSnapshot();
125297
- const incomingTransfers = pendingIncomingWalletTransfers(pendingTransfers2);
125506
+ const incomingTransfers = pendingIncomingTransfers(pendingTransfers2);
125298
125507
  const claimCandidates = pendingIncomingClaimCandidates(incomingTransfers);
125299
125508
  if (!incomingTransfers.length) {
125300
125509
  markDone(diag, "wallet.pendingTxs.active", startedAt, { pending: 0, claimed: 0, reason });
@@ -125639,7 +125848,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125639
125848
  if (cacheWriteRef.current.timer) {
125640
125849
  clearTimeout(cacheWriteRef.current.timer);
125641
125850
  }
125642
- cacheWriteRef.current = { timer: null, cache: null, state: null };
125851
+ cacheWriteRef.current = { timer: null, cache: null, state: null, fullHistory: false };
125643
125852
  cacheDirtyRef.current = false;
125644
125853
  };
125645
125854
  const startCacheHydration = () => {
@@ -125697,12 +125906,10 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125697
125906
  if (!cacheDirtyRef.current && !cacheWriteRef.current.timer) {
125698
125907
  return;
125699
125908
  }
125700
- if (historyTransfersRef.current.length < knownHistoryCountRef.current) {
125701
- return;
125702
- }
125703
125909
  cacheDirtyRef.current = false;
125704
125910
  cacheWriteRef.current.cache = localCache;
125705
125911
  cacheWriteRef.current.state = transferStore.getCacheState();
125912
+ cacheWriteRef.current.fullHistory = historyTransfersRef.current.length >= knownHistoryCountRef.current;
125706
125913
  if (cacheWriteRef.current.timer) {
125707
125914
  clearTimeout(cacheWriteRef.current.timer);
125708
125915
  }
@@ -125731,7 +125938,7 @@ function createWalletTransferSession({ wallet, walletPK, localCache, claimIncomi
125731
125938
  serverHistoryComplete: serverHistoryCompleteRef.current,
125732
125939
  hasMoreTxs: !historyComplete,
125733
125940
  hasPendingTxs,
125734
- hasPendingIncomingTxs: pendingIncomingWalletTransfers(transfersRef.current).length > 0,
125941
+ hasPendingIncomingTxs: pendingIncomingTransfers(transfersRef.current).length > 0,
125735
125942
  beginSdkActivity,
125736
125943
  getRecentTxs,
125737
125944
  claimPendingIncomingTransfers: claimPendingIncomingTransfers2,
@@ -126087,6 +126294,7 @@ function createWalletSession({
126087
126294
  deferBackgroundWork = null,
126088
126295
  appState = null,
126089
126296
  transferStore,
126297
+ cachedBalance = null,
126090
126298
  ghostWallet = true,
126091
126299
  diag = null
126092
126300
  }) {
@@ -126104,6 +126312,7 @@ function createWalletSession({
126104
126312
  const txSubscriptions = createFieldSubscriptions(() => publishedSnapshot?.txValue);
126105
126313
  let polling = null;
126106
126314
  let readyDiag = null;
126315
+ let refreshWalletBalance = null;
126107
126316
  const transferSession = createWalletTransferSession({
126108
126317
  wallet,
126109
126318
  walletPK,
@@ -126116,6 +126325,7 @@ function createWalletSession({
126116
126325
  appState,
126117
126326
  deferBackgroundWork,
126118
126327
  backgroundSignal: backgroundAbort.signal,
126328
+ refreshBalance: () => refreshWalletBalance?.(),
126119
126329
  diag
126120
126330
  });
126121
126331
  const getTransferState = () => transferSession.getSnapshot();
@@ -126135,7 +126345,16 @@ function createWalletSession({
126135
126345
  listener();
126136
126346
  }
126137
126347
  };
126138
- 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;
126139
126358
  const funding = createFundingAddress({ wallet, network, diag, onChange: publish });
126140
126359
  const updateWalletData = createWalletData({
126141
126360
  wallet,
@@ -126216,6 +126435,7 @@ function createWalletSession({
126216
126435
  wallet,
126217
126436
  network,
126218
126437
  balance: balanceState.balance,
126438
+ displayBalance: balanceState.displayBalance,
126219
126439
  satsBalance: balanceState.satsBalance,
126220
126440
  tokenBalances: balanceState.tokenBalances,
126221
126441
  fundingAddress: funding.getSnapshot(),
@@ -126314,7 +126534,7 @@ function createWalletSession({
126314
126534
  };
126315
126535
  }
126316
126536
  var init_session4 = __esm(() => {
126317
- init_balance();
126537
+ init_balance2();
126318
126538
  init_claims();
126319
126539
  init_funding();
126320
126540
  init_lightning();
@@ -126323,6 +126543,7 @@ var init_session4 = __esm(() => {
126323
126543
  init_send2();
126324
126544
  init_transfers2();
126325
126545
  init_withdraw();
126546
+ init_localdata();
126326
126547
  });
126327
126548
 
126328
126549
  // ../../shared/wallet/transferstore.js
@@ -126531,6 +126752,7 @@ function createTransferStore() {
126531
126752
  transfers: state.historyTransfers,
126532
126753
  walletPK: state.walletPK,
126533
126754
  serverEndReached: state.serverEndReached,
126755
+ historyCount: state.historyCount,
126534
126756
  nextOffset: state.nextOffset,
126535
126757
  oldestTxMs: state.serverEndReached ? state.oldestVerifiedTxMs : null
126536
126758
  };
@@ -170014,9 +170236,13 @@ function peerRecordFromDoc(snap) {
170014
170236
  function peerRecordsFromSnapshot(snapshot) {
170015
170237
  return snapshot.docs.map(peerRecordFromDoc).filter(Boolean);
170016
170238
  }
170017
- function avatarPath(uid) {
170239
+ function avatarPath(uid, version8) {
170018
170240
  requireUid(uid);
170019
- 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`;
170020
170246
  }
170021
170247
  function reportEvidencePath(reporter, targetUid, evidenceId) {
170022
170248
  if (!reporter || !targetUid || !evidenceId) {
@@ -170082,6 +170308,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170082
170308
  }
170083
170309
  let activeVaultSigner = null;
170084
170310
  const localSessionTerminations = new Set;
170311
+ const pendingAvatars = new Map;
170085
170312
  function pageTs(value) {
170086
170313
  if (value instanceof Timestamp2) {
170087
170314
  return value;
@@ -170350,6 +170577,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170350
170577
  }, onError);
170351
170578
  }
170352
170579
  async function logout() {
170580
+ pendingAvatars.clear();
170353
170581
  await signOut(requireAuth());
170354
170582
  return true;
170355
170583
  }
@@ -170483,25 +170711,45 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170483
170711
  await callFunction("acceptAgreement");
170484
170712
  return true;
170485
170713
  }
170486
- async function writeProfileAvatar(uid, avatar) {
170714
+ async function setProfileAvatar(uid, data) {
170487
170715
  requireUid(uid);
170488
- await updateDoc(doc(db, "profiles", uid), { avatar });
170489
- return true;
170490
- }
170491
- async function uploadProfileAvatar(uid, data, { contentType = "image/webp" } = {}) {
170492
- const targetStorage = requireStorage2();
170493
- const path = avatarPath(uid);
170494
- 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 });
170495
- const url = await getDownloadURL(ref(targetStorage, path));
170496
- return {
170497
- url,
170498
- generation: result?.metadata?.generation ?? result?.generation ?? null
170499
- };
170500
- }
170501
- async function deleteProfileAvatar(uid) {
170502
- const targetStorage = requireStorage2();
170503
- await deleteObject(ref(targetStorage, avatarPath(uid)));
170504
- 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
+ }
170505
170753
  }
170506
170754
  async function writeProfileIdentity(identity) {
170507
170755
  if (!identity || typeof identity !== "object" || Array.isArray(identity)) {
@@ -170638,13 +170886,28 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
170638
170886
  onUpdate?.(snap.exists() ? snap.data()?.active === true : false, { exists: snap.exists() });
170639
170887
  }, onError);
170640
170888
  }
170641
- async function readPeerAvatar(uid) {
170642
- 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));
170643
170907
  }
170644
170908
  async function peerAvatarUrl(uid, { version: version8 = null } = {}) {
170645
170909
  const targetStorage = requireStorage2();
170646
- const url = await getDownloadURL(ref(targetStorage, avatarPath(uid)));
170647
- return avatarUrlWithVersion(url, version8);
170910
+ return getDownloadURL(ref(targetStorage, avatarPath(uid, version8)));
170648
170911
  }
170649
170912
  function profileQueryField(field2, { network } = {}) {
170650
170913
  if (field2 === "walletPK") {
@@ -171103,12 +171366,13 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
171103
171366
  }
171104
171367
  };
171105
171368
  }
171106
- async function pushInbox(recipientUid, ping) {
171369
+ async function pushInbox(recipientUid, ping, options2 = {}) {
171107
171370
  requireUid(recipientUid);
171108
171371
  if (!ping)
171109
171372
  throw new Error("inbox ping required");
171110
171373
  await callFunction("push", {
171111
171374
  recipientUid,
171375
+ notify: options2?.notify !== false,
171112
171376
  ping: {
171113
171377
  v: ping.v,
171114
171378
  epk: ping.epk,
@@ -171147,7 +171411,9 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
171147
171411
  payload.onCommitted?.({ chatId, messageId });
171148
171412
  let delivered = true;
171149
171413
  if (payload.inbox?.recipientUid && payload.inbox?.ping) {
171150
- 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);
171151
171417
  }
171152
171418
  return { chatId, messageId, delivered };
171153
171419
  }
@@ -171400,18 +171666,19 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
171400
171666
  }
171401
171667
  async function banAdminUser(uid, feature = "chat") {
171402
171668
  requireUid(uid);
171403
- await setDoc(doc(db, "moderation", uid), { banned: { [feature]: { until: null } } }, { merge: true });
171404
171669
  if (feature === "avatar") {
171405
- await deleteProfileAvatar(uid).catch((error) => {
171406
- if (error?.code !== "storage/object-not-found") {
171407
- throw error;
171408
- }
171409
- });
171670
+ await callFunction("setAvatarModeration", { uid, banned: true });
171671
+ return true;
171410
171672
  }
171673
+ await setDoc(doc(db, "moderation", uid), { banned: { [feature]: { until: null } } }, { merge: true });
171411
171674
  return true;
171412
171675
  }
171413
171676
  async function unbanAdminUser(uid, feature = "chat") {
171414
171677
  requireUid(uid);
171678
+ if (feature === "avatar") {
171679
+ await callFunction("setAvatarModeration", { uid, banned: false });
171680
+ return true;
171681
+ }
171415
171682
  await setDoc(doc(db, "moderation", uid), { banned: { [feature]: deleteField() } }, { merge: true });
171416
171683
  return true;
171417
171684
  }
@@ -171449,9 +171716,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
171449
171716
  write: writeProfileIdentity
171450
171717
  },
171451
171718
  avatar: {
171452
- write: writeProfileAvatar,
171453
- upload: uploadProfileAvatar,
171454
- delete: deleteProfileAvatar
171719
+ set: setProfileAvatar
171455
171720
  }
171456
171721
  },
171457
171722
  private: {
@@ -174041,13 +174306,6 @@ function cleanMessage(value, limit2, label = "message") {
174041
174306
  throw new Error(`${label} must be ${limit2} characters or fewer`);
174042
174307
  return message;
174043
174308
  }
174044
- function readAvatarGeneration(value) {
174045
- const version8 = Number(value);
174046
- if (!Number.isSafeInteger(version8) || version8 <= 0) {
174047
- throw new Error("avatar upload did not return a valid generation");
174048
- }
174049
- return version8;
174050
- }
174051
174309
  function passkeyRows(linksList, passkeys, currentPasskeyId) {
174052
174310
  return [...linksList, ...passkeys].sort((left, right) => (right.createdAt || 0) - (left.createdAt || 0)).map((row) => ({
174053
174311
  ...row,
@@ -174135,23 +174393,17 @@ function createRuntimeProductActions({
174135
174393
  const user = await getUser();
174136
174394
  if (!data)
174137
174395
  throw new Error("avatar data required");
174138
- const result = await cloud.user.profile.avatar.upload(user.uid, data, {
174139
- contentType: options2.contentType || "image/webp"
174140
- });
174141
- const avatarVersion = readAvatarGeneration(result?.generation);
174142
- await cloud.user.profile.avatar.write(user.uid, avatarVersion);
174143
- await user.refetchAvatar?.({ version: avatarVersion, optimistic: true });
174144
- 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 };
174145
174403
  },
174146
174404
  async deleteAvatar() {
174147
174405
  const user = await getUser();
174148
- try {
174149
- await cloud.user.profile.avatar.delete(user.uid);
174150
- } catch (error) {
174151
- if (error?.code !== "storage/object-not-found")
174152
- throw error;
174153
- }
174154
- await cloud.user.profile.avatar.write(user.uid, null);
174406
+ await cloud.user.profile.avatar.set(user.uid, null);
174155
174407
  user.clearAvatar?.();
174156
174408
  return { deleted: true };
174157
174409
  }