@ghostlytools/cli 1.1.5 → 1.1.7

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.
@@ -1,8 +1,8 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { $a as parseLocalTarget, $i as parseLinkPreview, $o as readHandoffPake, $r as EDIT_CAPABILITY, $s as readTurnPacket, $t as TEST_USDT_FAUCET_AMOUNT, A as knockIdentity, Ac as toZ32, Ao as handoffAttemptFailed, As as encodeInviteCode, B as GROUP_READ_NOTE, Bn as decodeBolt11, Bo as handoffManifestFrames, Br as readPin, Bs as devicePingFrame, C as communityHasFrame, Ca as statusCardText, Cc as verify, Ci as revokesIdentity, Co as HANDOFF_PAKE, Cr as readLobby, Cs as verifyEnrollProof, D as decodeGroupEntryLink, Da as receivedTimestamp, Dc as fromBase64Url, Di as validExternalKey, Do as HANDOFF_VERIFIED, Dr as transferEnded, Ds as OwnInviteError, Ea as heardTime, Ec as bytesEqual, Ei as emptyProofLedger, Eo as HANDOFF_TIMINGS, Er as FILE_LIMITS, Es as readDeviceInvite, F as GroupWakeLimiter, Fa as isAppRef, Fi as DHT_FILE_CAPABILITY, Fo as handoffContextProof, Fr as PIN_LIMITS, Fs as HANDOFF_CAPABILITY, G as carryMentions, Ga as verifyCommitSignature, Gi as fileMessageText, Gn as isFederationId, Go as handoffStreamKey, Gr as parseWakeFrame, Gs as TURN_RAISE_READ_TIMEOUT_MS, Hn as cashuRequestPayload, Ho as handoffPakeFrame, Hr as WakeLimiter, Hs as seedSigner, Ht as didDhtFromPublicKey, I as groupWakeFrame, Ia as readAppRevocations, Ii as dhtFileId, Io as handoffDataFrame, Ir as mayPin, Is as deviceEchoFrame, J as groupName, Ji as parseVideoMeta, Jn as GhostLink, Jo as readHandoffAck, Jr as checkPushEndpoint, Js as turnPutSummary, Jt as EVM_TEST_CHAINS, K as parseCommunityEdit, Ka as edgeParams, Ki as parseVoiceMeta, Kn as parseCashuRequestPayload, Ko as handoffVersions, Kr as relayRequest, Ks as TURN_SETTLE_MS, Kt as signDidDhtPacket, L as groupWakes, Li as parseImageMeta, Lo as handoffDigest, Lr as nextPinNumber, Ls as deviceEchoNonce, M as mergeKnocks, Ma as appRef, Mc as utf8Encode, Mo as handoffBusyFrame, Ms as inviteOwnership, N as readKnocks, Ni as DHT_MESSAGE_TTL, No as handoffCancelFrame, Nr as typingActivity, Ns as DEVICES_CAPABILITY, O as encodeGroupEntryLink, Oa as shownTime, Oc as randomBytes, Oi as TRANSPORTS, Oo as filePartName, Or as transferStalled, Os as createChatInvite, Pa as isAppKey, Pi as peekDhtMailbox, Po as handoffContext, Ps as ENROLL_CAPABILITY, Qa as formatLocalTarget, Qi as dhtEditId, Qn as CarriedTransport, Qo as readHandoffHello, Qs as nextTurnPosition, Qt as SEPOLIA_TEST_USDT_FAUCET, R as parseGroupWakeFrame, Rn as engineError, Ro as handoffEphemeral, Rr as pinIsNewer, Rs as deviceKeyZ32, S as GROUP_READ_NOTE_COMMUNITY, Sa as sameButtonText, Sc as publicKeyFromZ32, Si as receivedIdentityStatus, So as HANDOFF_OFFER, Sr as readBeaconHead, Ss as readEnrollHello, T as encodeCommunityLink, Ta as claimedTime, Tc as sanitizeAvatar, Ti as PeerProofs, To as HANDOFF_REQUEST, Tr as ChatFiles, Ts as deviceInviteJoinerParams, Ua as MEMBER_KEY, Un as fedimintRequestPayload, Uo as handoffReleaseFrame, Ur as deviceWakeRequest, Us as webCryptoSigner, Ut as encodeDidDhtDeactivated, V as GroupSession, Va as GROUP_MEMBER_CAP, Vn as ENDPOINT, Vo as handoffOfferFrame, Vr as WAKE_CALL_INTERVAL_MS, Vs as newDeviceSetSecret, Vt as didDhtDocument, W as GROUP_EDIT_TEXT_BYTES, Wa as rosterHas, Wn as findEndpoint, Wo as handoffRetryAfter, Wr as newWakeToken, Wt as encodeDidDhtPacket, Xa as presenceSeenAt, Xi as EDIT_RATE_WINDOW_MS, Xn as LIVENESS_PING_MS, Xo as readHandoffDone, Xr as chatAppId, Xs as TURN_MAX, Xt as PaymentPreflightError, Ya as RELAY_POLL_INTERVALS, Yi as EDIT_BUFFER_MS, Yn as INVITE_TAKEN, Yo as readHandoffCancel, Yr as vapidKeysMatch, Ys as TURN_LAST_SEQUENCE, Yt as ONCHAIN_PROVIDER, Za as GhostlyHttpError, Zi as RateWindow, Zn as pairedMessageFrame, Zo as readHandoffHave, Zr as isAppVersion, Zs as firstDeviceSetSecret, Zt as SEPOLIA_TEST_USDT, _ as groupTypingEpoch, _o as HANDOFF_ACK, _r as mergeLobby, _s as enrollDoneFrame, aa as ReactionWindow, ac as RelayTransport, ai as HoldRefusedError, an as validatePaymentTarget, ao as RTC_CONFIG, ar as MESH_HUBS, as as readSetUpdate, b as COMMUNITY_LIMITS, bc as identityFromSeed, bi as identityStatement, bo as HANDOFF_HAVE, br as rankHubs, bs as enrollProofFrame, c as APP_BUNDLE_LIMITS, ca as reactionEmoji, ci as CapsExchange, cn as SPARK_PROVIDER, cr as pickMeshHubs, cs as signTombstone, da as validReactionNumber, dc as isDiscoveryBudgetError, di as dialDescriptors, dn as isBitcoinAddress, do as parseCallSignal, dr as beaconRecords, ea as forwardedAgain, ec as signTurnPacket, ei as UPGRADE_CAPABILITY, en as USDT_PUBLIC_RPC, eo as webLocalFetch, es as readHandoffTurnFrame, fa as wireReaction, fc as withRequestOptions, fr as doorHubs, g as GroupTypingBoard, ga as BUTTON_ID, gi as IdentityExchange, go as serviceIdFromName, gr as mergeBeacon, gs as enrollDigits, ha as replySnippet, hc as createRelayPayload, ho as LEGACY_SERVICES, hr as lobbyRecords, hs as enrollCancelReason, ht as normalizeAtprotoHandle, i as planAppUpdate, ia as REACTION_LIMITS, ic as DEFAULT_RELAYS, ii as HoldKeys, in as formatPaymentAmount, ir as readGroupSignal, j as knockRecords, ja as appFingerprint, jc as utf8Decode, jo as handoffAttemptSucceeded, jr as TypingSender, k as entryParams, kc as toBase64Url, ki as automaticTransport, ko as handoffAttemptAllowed, ks as decodeInviteCode, l as APP_BUNDLE_MAGIC, la as reactionIsNewer, lc as traceLink, li as DHT_TEXT_CAPABILITY, ln as sparkAddressKind, lr as COMMUNITY_TOPOLOGY, ls as verifySetUpdate, m as readAppBundle, ma as readReply, mi as IDENTITY_REVOCATION_LABEL, mo as HTTP_SERVICE_PROTO, mr as lobbyKeys, ms as enrollCancelFrame, mt as isAtprotoDid, n as appUpdateDecision, na as COMMUNITY_REACTION_FRAME, nc as turnKeys, ni as HOLD_DATED_BACK_MS, nn as assertTokenUnits, no as decodeControl, ns as sealHandoffPiece, o as readAppStore, oa as nextReactionNumber, oc as currentRelays, oi as newMailbox, on as walletNetworkOf, or as mayBeHub, os as setAckFrame, p as isAppUrl, pa as pairedReplyAuthor, pc as PacketTooLargeError, pn as isPubkyKey, po as emptyLinkRecords, pr as freshHubs, q as mentionsMember, qa as DirectPathWatch, qn as parseFedimintRequestPayload, qo as newHandoffId, qr as wakeRequest, qs as classifyTurnRead, qt as ETHEREUM_USDT, r as checkAppBeforeRun, ra as GROUP_REACTION_FRAME, rc as turnName, ri as HOLD_LIMITS, rn as assertWholeSats, ro as encodeControl, rr as groupSignalFrame, sa as queueReaction, sc as normalizeRelayUrl, si as readManifest, sn as SPARK_NETWORKS, sr as meshHubs, ss as setUpdateFrame, t as appStoreDecision, ta as readForwarded, tc as signTurnRelease, ti as HOLD_CAPABILITY, tn as WALLET_NETWORKS, to as LIMITS, ts as readHandoffVerified, u as appDigest, ua as readReaction, uc as budgetRetryMs, ui as capsDescriptors, un as sparkInvoiceDetails, ur as beaconKeys, v as openGroupTyping, vi as emptyIdentityLedger, vo as HANDOFF_CANCEL, vr as newerHead, vs as enrollGrantFrame, w as decodeCommunityLink, wa as arrivalKey, wi as lastSharedWithMe, wo as HANDOFF_PIECE_BYTES, wr as shouldBeHub, ws as createDeviceInvite, x as CommunitySession, xa as checkStatusCard, xc as identityFromSeedB64, xi as newIdentityBinding, xo as HANDOFF_HELLO, xr as readBeacon, xs as readEnrollGrant, y as sealGroupTyping, ya as STATUS_CARD_LIMITS, yc as createIdentity, yi as identityRevocationValue, yo as HANDOFF_DONE, yr as pickHubs, ys as enrollHelloFrame, za as canonicalJson, zi as safeBlobType, zn as engineText, zo as handoffHelloFrame, zr as pinNumberHolds, zs as deviceLinkPairing } from "./src-C6qQYrAH.js";
2
+ import { $i as formatFileSize, $n as GROUP_FILE_LIMITS, $o as handoffEphemeral, $r as WAKE_CALL_INTERVAL_MS, $s as deviceKeyZ32, $t as SEPOLIA_TEST_USDT_FAUCET, A as entryParams, Aa as replySnippet, Ac as createRelayPayload, Ai as IdentityExchange, Ao as LEGACY_SERVICES, Ar as newerHead, As as enrollCancelReason, Ba as claimedTime, Bc as sanitizeAvatar, Bi as emptyProofLedger, Bn as engineText, Bo as HANDOFF_REQUEST, Br as transferStalled, Bs as deviceInviteJoinerParams, C as GROUP_READ_NOTE_COMMUNITY, Ca as reactionEmoji, Ci as DHT_TEXT_CAPABILITY, Cr as beaconRecords, Cs as signTombstone, Da as wireReaction, Dc as withRequestOptions, Dr as lobbyRecords, E as encodeCommunityLink, Ea as validReactionNumber, Ec as isDiscoveryBudgetError, Eo as parseCallSignal, Er as lobbyKeys, Fc as identityFromSeed, Fi as newIdentityBinding, Fo as HANDOFF_HAVE, Fr as readLobby, Fs as enrollProofFrame, G as GROUP_EDIT_TEXT_BYTES, Gc as toZ32, Gn as findEndpoint, Go as handoffAttemptFailed, Gr as typingActivity, Gs as encodeInviteCode, Gt as encodeDidDhtPacket, H as GroupSession, Ha as receivedTimestamp, Hc as fromBase64Url, Hi as TRANSPORTS, Hn as ENDPOINT, Ho as HANDOFF_VERIFIED, Hs as OwnInviteError, Ht as didDhtDocument, I as GroupWakeLimiter, Ia as checkStatusCard, Ic as identityFromSeedB64, Ii as receivedIdentityStatus, Io as HANDOFF_HELLO, Ir as shouldBeHub, Is as readEnrollGrant, J as mentionsMember, Ji as peekDhtMailbox, Jn as parseFedimintRequestPayload, Jo as handoffCancelFrame, Jr as mayPin, Js as DEVICES_CAPABILITY, Jt as ETHEREUM_USDT, K as carryMentions, Ka as appFingerprint, Kc as utf8Decode, Kn as isFederationId, Ko as handoffAttemptSucceeded, L as groupWakeFrame, La as sameButtonText, Lc as publicKeyFromZ32, Li as revokesIdentity, Lo as HANDOFF_OFFER, Lr as ChatFiles, Ls as readEnrollHello, M as knockRecords, Mi as emptyIdentityLedger, Mo as HANDOFF_ACK, Mr as rankHubs, Ms as enrollDoneFrame, N as mergeKnocks, Ni as identityRevocationValue, No as HANDOFF_CANCEL, Nr as readBeacon, Ns as enrollGrantFrame, O as decodeGroupEntryLink, Oa as pairedReplyAuthor, Oc as PacketTooLargeError, Oi as IDENTITY_REVOCATION_LABEL, Oo as emptyLinkRecords, Or as mergeBeacon, P as readKnocks, Pa as STATUS_CARD_LIMITS, Pc as createIdentity, Pi as identityStatement, Po as HANDOFF_DONE, Pr as readBeaconHead, Ps as enrollHelloFrame, Qn as pairedMessageFrame, Qo as handoffDigest, Qr as readPin, Qs as deviceEchoNonce, Qt as SEPOLIA_TEST_USDT, R as groupWakes, Ra as statusCardText, Rc as verify, Ri as lastSharedWithMe, Ro as HANDOFF_PAKE, Rr as FILE_LIMITS, Rs as verifyEnrollProof, S as CommunitySession, Sa as queueReaction, Sc as normalizeRelayUrl, Si as CapsExchange, Sr as beaconKeys, Ss as setUpdateFrame, T as decodeCommunityLink, Ta as readReaction, Tc as budgetRetryMs, Ti as dialDescriptors, Tr as freshHubs, Ua as shownTime, Uc as randomBytes, Ui as automaticTransport, Un as cashuRequestPayload, Uo as filePartName, Ur as TypingSender, Us as createChatInvite, Ut as didDhtFromPublicKey, V as GROUP_READ_NOTE, Va as heardTime, Vc as bytesEqual, Vi as validExternalKey, Vn as decodeBolt11, Vo as HANDOFF_TIMINGS, Vs as readDeviceInvite, Wc as toBase64Url, Wn as fedimintRequestPayload, Wo as handoffAttemptAllowed, Ws as decodeInviteCode, Wt as encodeDidDhtDeactivated, Xa as isAppRef, Xi as dhtFileId, Xn as INVITE_TAKEN, Xo as handoffContextProof, Xr as pinIsNewer, Xs as HANDOFF_CAPABILITY, Xt as ONCHAIN_PROVIDER, Y as groupName, Ya as isAppKey, Yi as DHT_FILE_CAPABILITY, Yn as GhostLink, Yo as handoffContext, Yr as nextPinNumber, Ys as ENROLL_CAPABILITY, Yt as EVM_TEST_CHAINS, Za as readAppRevocations, Zi as parseImageMeta, Zn as LIVENESS_PING_MS, Zo as handoffDataFrame, Zr as pinNumberHolds, Zs as deviceEchoFrame, Zt as PaymentPreflightError, _ as GroupTypingBoard, _a as COMMUNITY_REACTION_FRAME, _c as turnKeys, _i as HOLD_LIMITS, _o as decodeControl, _r as MESH_HUBS, _s as sealHandoffPiece, ai as wakeRequest, an as formatPaymentAmount, ao as rosterHas, as as handoffRetryAfter, b as sealGroupTyping, ba as ReactionWindow, bc as RelayTransport, bi as newMailbox, bo as RTC_CONFIG, br as pickMeshHubs, bs as readSetUpdate, c as APP_BUNDLE_LIMITS, cc as classifyTurnRead, ci as chatAppId, cn as SPARK_NETWORKS, co as DirectPathWatch, cs as newHandoffId, d as appViewOf, da as EDIT_RATE_WINDOW_MS, dc as TURN_MAX, di as BUTTONS_CAPABILITY, dn as sparkInvoiceDetails, do as presenceSeenAt, ds as readHandoffDone, ea as safeBlobType, ec as deviceLinkPairing, ei as WakeLimiter, en as TEST_USDT_FAUCET_AMOUNT, eo as canonicalJson, es as handoffHelloFrame, fa as RateWindow, fc as firstDeviceSetSecret, fi as EDIT_CAPABILITY, fn as isBitcoinAddress, fo as GhostlyHttpError, fs as readHandoffHave, ga as readForwarded, gc as signTurnRelease, gi as HOLD_DATED_BACK_MS, go as LIMITS, gr as readGroupSignal, gs as readHandoffVerified, gt as normalizeAtprotoHandle, h as readAppBundle, ha as forwardedAgain, hc as signTurnPacket, hi as HOLD_CAPABILITY, ho as webLocalFetch, hr as groupSignalFrame, hs as readHandoffTurnFrame, ht as isAtprotoDid, i as planAppUpdate, ic as webCryptoSigner, ii as relayRequest, in as assertWholeSats, io as MEMBER_KEY, ir as groupFileFallback, is as handoffReleaseFrame, j as knockIdentity, ja as BUTTON_ID, jo as serviceIdFromName, jr as pickHubs, js as enrollDigits, k as encodeGroupEntryLink, ka as readReply, ko as HTTP_SERVICE_PROTO, kr as mergeLobby, ks as enrollCancelFrame, l as APP_BUNDLE_MAGIC, la as parseVideoMeta, lc as turnPutSummary, li as isAppVersion, ln as SPARK_PROVIDER, ls as readHandoffAck, m as isAppUrl, ma as parseLinkPreview, mc as readTurnPacket, mi as UPGRADE_CAPABILITY, mn as isPubkyKey, mo as parseLocalTarget, ms as readHandoffPake, n as appUpdateDecision, nc as newDeviceSetSecret, ni as newWakeToken, nn as WALLET_NETWORKS, no as GROUP_MEMBER_CAP, ns as handoffOfferFrame, o as readAppStore, oa as fileMessageText, oc as TURN_RAISE_READ_TIMEOUT_MS, oi as checkPushEndpoint, on as validatePaymentTarget, oo as verifyCommitSignature, or as readGroupFileMeta, os as handoffStreamKey, pa as dhtEditId, pc as nextTurnPosition, pi as STATUS_CARD_CAPABILITY, po as formatLocalTarget, ps as readHandoffHello, q as parseCommunityEdit, qa as appRef, qc as utf8Encode, qi as DHT_MESSAGE_TTL, qn as parseCashuRequestPayload, qo as handoffBusyFrame, qr as PIN_LIMITS, qs as inviteOwnership, qt as signDidDhtPacket, r as checkAppBeforeRun, rc as seedSigner, ri as parseWakeFrame, rn as assertTokenUnits, rs as handoffPakeFrame, sa as parseVoiceMeta, sc as TURN_SETTLE_MS, si as vapidKeysMatch, sn as walletNetworkOf, so as edgeParams, ss as handoffVersions, t as appStoreDecision, tc as devicePingFrame, ti as deviceWakeRequest, tn as USDT_PUBLIC_RPC, ts as handoffManifestFrames, u as appDigest, ua as EDIT_BUFFER_MS, uc as TURN_LAST_SEQUENCE, ui as APPS_CAPABILITY, un as sparkAddressKind, uo as RELAY_POLL_INTERVALS, ur as CarriedTransport, us as readHandoffCancel, v as groupTypingEpoch, va as GROUP_REACTION_FRAME, vc as turnName, vi as HoldKeys, vo as encodeControl, vr as mayBeHub, w as communityHasFrame, wa as reactionIsNewer, wc as traceLink, wi as capsDescriptors, wr as doorHubs, ws as verifySetUpdate, x as COMMUNITY_LIMITS, xa as nextReactionNumber, xc as currentRelays, xi as readManifest, xr as COMMUNITY_TOPOLOGY, xs as setAckFrame, y as openGroupTyping, ya as REACTION_LIMITS, yc as DEFAULT_RELAYS, yi as HoldRefusedError, yr as meshHubs, z as parseGroupWakeFrame, za as arrivalKey, zi as PeerProofs, zn as engineError, zo as HANDOFF_PIECE_BYTES, zr as transferEnded, zs as createDeviceInvite } from "./src-Cp3o_yHN.js";
3
3
  import { a as clearProfileStores, c as fileStore, d as store, f as transact, i as STORES$1, l as openDb, n as APP_STORAGE_SCOPE_INDEX, o as databaseName, p as wrap, r as CARD_INDEX, s as deleteDatabase, t as APP_STORAGE_CHAT_INDEX, u as profileOpenFailure } from "./idb-BIdus0WX.js";
4
4
  import { a as fileBytesOf, c as removeFileBytes, i as fileBytes, l as FILE_BYTES_STEP, n as blobDigest } from "./fileBytes-3AQ2tJzW.js";
5
- import { $ as BACKUP_MEDIA_TYPE, A as requireTurn, At as streamStored, B as networkLabel, C as breezDatabasesFor, Ct as replyTo, D as sourceKey, Dt as readStored$1, E as ProviderSources, Et as FileAppender, F as sealSeed, G as BITCOIN_INVOICE_ON_TESTNET, H as cashuMint, I as unsealSeed, J as fakesLightning, K as DEFAULT_MINTS, L as ModeChanged, M as setSingleWriterGate, N as intentRepository, O as awayFrom, Ot as removeStored, P as newDeviceKey, Q as paysItsOwnInvoices, R as ModeGate, S as breezDatabaseInUse, St as replyRef, T as lightningPaying, Tt as paymentWireId, V as CASHU_MINT_SOURCE, W as WALLET_TYPES, X as isWorthlessMint, Y as isTestMint, Z as mintNetwork, _ as LightningCards, _t as verifyIdentity, a as irohRelayProblem, at as reserveAdapterIds, b as normalizePhrase, bt as pairedWireReply, c as createFailure, ct as SourceConfigError, d as paymentNetwork, dt as networkMode, et as MAX_BACKUP_BYTES, f as paymentNetworksOf, ft as redact, g as CASHU_CARD, gt as boundedIdentityFetch, h as rpcHost, ht as setOwnDidSource, i as createIrohWebEndpoint, it as registeredOnchainProviders, j as setAwayWallets, k as refuseAway, kt as storedSize, l as createTiming, lt as SourceUnreachableError, m as networkReason, mt as resolveAtprotoDid, n as loadFedimintSdk, nt as onAdaptersChanged, o as WALLET_NAMES, ot as NothingSpentError, p as walletInstances, pt as assertPublicServer, q as TEST_MINT, r as DEFAULT_IROH_RELAYS, rt as registeredLightningProviders, s as assertConfirmedReal, st as PROVIDER_PLATFORMS, t as fedimintDatabase, tt as identityProviders, u as crossNetwork, ut as isNothingSpentError, v as cardsKey, vt as chosenResolver, w as dropBreezDatabase, wt as paymentAlias, x as breezDatabase, xt as receivedPairedReply, y as isRecoveryPhrase, yt as groupReply, z as WrongNetworkError } from "../ghostly.mjs";
5
+ import { $ as BACKUP_MEDIA_TYPE, A as requireTurn, At as paymentWireId, B as networkLabel, C as breezDatabasesFor, Ct as storedGroupFiles, D as sourceKey, Dt as replyRef, E as ProviderSources, Et as receivedPairedReply, F as sealSeed, Ft as streamStored, G as BITCOIN_INVOICE_ON_TESTNET, H as cashuMint, I as unsealSeed, J as fakesLightning, K as DEFAULT_MINTS, L as ModeChanged, M as setSingleWriterGate, Mt as readStored$1, N as intentRepository, Nt as removeStored, O as awayFrom, Ot as replyTo, P as newDeviceKey, Pt as storedSize, Q as paysItsOwnInvoices, R as ModeGate, S as breezDatabaseInUse, St as groupOfFile, T as lightningPaying, Tt as pairedWireReply, V as CASHU_MINT_SOURCE, W as WALLET_TYPES, X as isWorthlessMint, Y as isTestMint, Z as mintNetwork, _ as LightningCards, _t as verifyIdentity, a as irohRelayProblem, at as reserveAdapterIds, b as normalizePhrase, bt as groupFileId, c as createFailure, ct as SourceConfigError, d as paymentNetwork, dt as networkMode, et as MAX_BACKUP_BYTES, f as paymentNetworksOf, ft as redact, g as CASHU_CARD, gt as boundedIdentityFetch, h as rpcHost, ht as setOwnDidSource, i as createIrohWebEndpoint, it as registeredOnchainProviders, j as setAwayWallets, jt as FileAppender, k as refuseAway, kt as paymentAlias, l as createTiming, lt as SourceUnreachableError, m as networkReason, mt as resolveAtprotoDid, n as loadFedimintSdk, nt as onAdaptersChanged, o as WALLET_NAMES, ot as NothingSpentError, p as walletInstances, pt as assertPublicServer, q as TEST_MINT, r as DEFAULT_IROH_RELAYS, rt as registeredLightningProviders, s as assertConfirmedReal, st as PROVIDER_PLATFORMS, t as fedimintDatabase, tt as identityProviders, u as crossNetwork, ut as isNothingSpentError, v as cardsKey, vt as chosenResolver, w as dropBreezDatabase, wt as groupReply, x as breezDatabase, xt as groupFileText, y as isRecoveryPhrase, yt as GroupFileDesk, z as WrongNetworkError } from "../ghostly.mjs";
6
6
  import { n as traceJoin } from "./joinTrace-CO8mlhNO.js";
7
7
  import { hkdf } from "@noble/hashes/hkdf.js";
8
8
  import { sha256 } from "@noble/hashes/sha2.js";
@@ -4392,14 +4392,14 @@ var BitcoinService = class {
4392
4392
  return this.target(await provider.receiveAddress(), (await provider.info()).network);
4393
4393
  }
4394
4394
  /**
4395
- * A transaction of the active source paying `target` at least `amount`, none of `claimed`. With a source
4396
- * that can list what an address received, that is the proof; otherwise the contact's `hint` txid must be
4397
- * one of the source's own receives of at least the amount.
4395
+ * A transaction of the active source paying `target` at least `amount`, none of `claimed`: what the source
4396
+ * says that address received is the only proof. A contact's txid proves nothing (it can name any receive of
4397
+ * the wallet), so a source that cannot list an address's receives never sees a request paid.
4398
4398
  */
4399
- async received(target, amount, claimed, hint) {
4399
+ async received(target, amount, claimed) {
4400
4400
  const provider = this.sources.active;
4401
- if (!provider || target.method !== "bitcoin" || (await provider.info()).network !== target.network) return void 0;
4402
- const tx = (provider.received ? await provider.received(target.address) : hint ? (await provider.history(100)).filter((tx) => tx.txid === hint) : []).find((tx) => tx.amount >= amount && !claimed.has(tx.txid) && /^[0-9a-f]{64}$/.test(tx.txid));
4401
+ if (!provider?.received || target.method !== "bitcoin" || (await provider.info()).network !== target.network) return void 0;
4402
+ const tx = (await provider.received(target.address)).find((tx) => tx.amount >= amount && !claimed.has(tx.txid) && /^[0-9a-f]{64}$/.test(tx.txid));
4403
4403
  return tx && {
4404
4404
  txid: tx.txid,
4405
4405
  confirmations: tx.confirmations
@@ -5748,6 +5748,24 @@ var BitcoindOnchain = class BitcoindOnchain {
5748
5748
  }
5749
5749
  return [...byTxid.values()].reverse().slice(0, Math.max(0, limit));
5750
5750
  }
5751
+ /** The wallet's receives on this address, newest first: only `receive` entries paying exactly it count. */
5752
+ async received(address) {
5753
+ const entries = await this.call("listtransactions", "*", 200, 0, true);
5754
+ if (!Array.isArray(entries)) throw new Error("The node returned no transaction list");
5755
+ const byTxid = /* @__PURE__ */ new Map();
5756
+ for (const raw of entries) {
5757
+ if (!raw || raw.category !== "receive" || raw.address !== address || typeof raw.txid !== "string" || !TXID.test(raw.txid)) continue;
5758
+ const tx = byTxid.get(raw.txid) ?? {
5759
+ txid: raw.txid,
5760
+ amount: 0,
5761
+ confirmations: typeof raw.confirmations === "number" ? Math.max(0, raw.confirmations) : 0
5762
+ };
5763
+ tx.amount += btcToSats(raw.amount);
5764
+ byTxid.delete(raw.txid);
5765
+ byTxid.set(raw.txid, tx);
5766
+ }
5767
+ return [...byTxid.values()].reverse();
5768
+ }
5751
5769
  async close() {
5752
5770
  this.closed = true;
5753
5771
  }
@@ -12685,6 +12703,7 @@ var Apps = class {
12685
12703
  version: app.manifest.version,
12686
12704
  title: app.manifest.title,
12687
12705
  permissions: [...app.permissions],
12706
+ view: appViewOf(bundle.manifest),
12688
12707
  entry: utf8Decode(entry)
12689
12708
  };
12690
12709
  }
@@ -12710,6 +12729,7 @@ var Apps = class {
12710
12729
  sequence: app.sequence,
12711
12730
  digest: app.digest,
12712
12731
  permissions: [...app.permissions],
12732
+ view: appViewOf(manifest),
12713
12733
  from: app.from,
12714
12734
  icon: manifest.files.some((f) => f.path === "icon.png"),
12715
12735
  installedAt: app.installedAt,
@@ -21635,14 +21655,17 @@ var db = {
21635
21655
  }
21636
21656
  },
21637
21657
  /**
21638
- * Every message of this profile that carries a task or a routine card, from every chat and group, oldest first (time,
21658
+ * Every message of this profile that carries a task, a routine or a usage card, from every chat and group, oldest first (time,
21639
21659
  * then id). Reads the card index only (`CARD_INDEX`): no chat's history is read, however long. The index follows the
21640
21660
  * rows, so an edit's card, a deleted message and a deleted chat or group are in step with no bookkeeping.
21641
21661
  */
21642
21662
  async getCardMessages() {
21643
21663
  const index = (await store(STORES$1.messages, "readonly")).index(CARD_INDEX);
21644
- const [tasks, routines] = await Promise.all([wrap(index.getAll("task")), wrap(index.getAll("routine"))]);
21645
- return [...tasks, ...routines].sort((a, b) => a.timestamp - b.timestamp || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
21664
+ return (await Promise.all([
21665
+ "task",
21666
+ "routine",
21667
+ "usage"
21668
+ ].map((kind) => wrap(index.getAll(kind))))).flat().sort((a, b) => a.timestamp - b.timestamp || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
21646
21669
  },
21647
21670
  /** Returns false when the message was already stored. */
21648
21671
  async addMessage(message) {
@@ -22275,12 +22298,28 @@ var Communities = class {
22275
22298
  now() {
22276
22299
  return this.time || Date.now();
22277
22300
  }
22301
+ /** Files and voice messages (WISP 503), the desk `Groups` keeps for every group. */
22302
+ files;
22278
22303
  has(groupId) {
22279
22304
  return this.stored.has(groupId);
22280
22305
  }
22306
+ ids() {
22307
+ return [...this.stored.keys()];
22308
+ }
22281
22309
  session(groupId) {
22282
22310
  return this.live.get(groupId)?.session;
22283
22311
  }
22312
+ /** Me in a community I am a member of, for its files: who is in it, and who could read one of its messages. */
22313
+ fileMembership(groupId) {
22314
+ const session = this.live.get(groupId)?.session;
22315
+ if (!session?.isMember) return void 0;
22316
+ return {
22317
+ me: session.myKey,
22318
+ community: true,
22319
+ inRoster: (key) => rosterHas(session.roster, key),
22320
+ couldRead: (key, messageId) => session.couldRead(key, messageId)
22321
+ };
22322
+ }
22284
22323
  /** Is this device a hub of the group? (tests and the load harness) */
22285
22324
  isHub(groupId) {
22286
22325
  const live = this.live.get(groupId);
@@ -22472,7 +22511,7 @@ var Communities = class {
22472
22511
  await this.store.putGroup(group);
22473
22512
  this.host.emit();
22474
22513
  }
22475
- async send(groupId, text, mentions = [], reply, forwarded, card) {
22514
+ async send(groupId, text, mentions = [], reply, forwarded, card, file) {
22476
22515
  const live = this.live.get(groupId);
22477
22516
  if (!live) return {
22478
22517
  error: "You are not in this group yet",
@@ -22483,7 +22522,7 @@ var Communities = class {
22483
22522
  refused: true
22484
22523
  };
22485
22524
  const now = this.now();
22486
- const result = card ? await live.session.sendText(text, this.host.myNick?.(), now, mentions, reply, forwarded, card) : await live.session.sendText(text, this.host.myNick?.(), now, mentions, reply, forwarded);
22525
+ const result = card || file ? await live.session.sendText(text, this.host.myNick?.(), now, mentions, reply, forwarded, card, file) : await live.session.sendText(text, this.host.myNick?.(), now, mentions, reply, forwarded);
22487
22526
  return "error" in result ? { error: result.error } : {
22488
22527
  error: null,
22489
22528
  messageId: result.id
@@ -23587,9 +23626,7 @@ var Communities = class {
23587
23626
  await this.store.putGroup(group);
23588
23627
  },
23589
23628
  broadcast: (frame) => this.hearers(id, frame).filter(([, linkId]) => this.sendTo(linkId, frame)).length,
23590
- direct: (to, frame) => {
23591
- if (this.mayHear(this.live.get(id), to, frame)) this.sendTo(this.host.edges(id).get(to), frame);
23592
- },
23629
+ direct: (to, frame) => this.mayHear(this.live.get(id), to, frame) ? this.sendTo(this.host.edges(id).get(to), frame) : void 0,
23593
23630
  addressed: (to, frame) => {
23594
23631
  const edges = this.host.edges(id), live = this.live.get(id);
23595
23632
  const direct = edges.get(to);
@@ -23604,10 +23641,15 @@ var Communities = class {
23604
23641
  message: async (m) => {
23605
23642
  const timestamp = m.timestamp;
23606
23643
  const mentioned = m.sender !== session.myKey && mentionsMember(m.mentions, session.myKey);
23644
+ const file = m.file && this.files ? await this.files.announced(id, {
23645
+ id: m.id,
23646
+ sender: m.sender,
23647
+ file: m.file
23648
+ }, session.myKey) : void 0;
23607
23649
  const stored = await this.host.storeMessage({
23608
23650
  linkId: MESSAGE_LINK$1(id),
23609
23651
  id: m.id,
23610
- text: m.text,
23652
+ text: file ? groupFileText(m.text, m.file) : m.text,
23611
23653
  sender: m.sender === session.myKey ? "me" : "peer",
23612
23654
  member: m.sender,
23613
23655
  timestamp,
@@ -23615,7 +23657,8 @@ var Communities = class {
23615
23657
  ...mentionFields(m.mentions, mentioned),
23616
23658
  ...m.reply && { replyTo: groupReply(m.reply, session.myKey) },
23617
23659
  ...m.forwarded && { forwarded: m.forwarded },
23618
- ...m.card && { card: m.card }
23660
+ ...m.card && { card: m.card },
23661
+ ...file && { file }
23619
23662
  });
23620
23663
  const came = m.sender === session.myKey ? timestamp : cameAt(timestamp, stored, arrivalNow(this.now()));
23621
23664
  this.lastMessageAt.set(id, Math.max(this.lastMessageAt.get(id) ?? 0, came));
@@ -23623,7 +23666,7 @@ var Communities = class {
23623
23666
  if (m.sender !== session.myKey) this.lastPeerMessageAt.set(id, Math.max(this.lastPeerMessageAt.get(id) ?? 0, came));
23624
23667
  noteCame(this.stored.get(id), came, m.sender !== session.myKey, mentioned);
23625
23668
  },
23626
- app: (m) => this.deliver(id, () => this.host.communityApp?.(id, m.sender, m.frame)),
23669
+ app: (m) => this.deliver(id, () => m.frame.t === "group-have" ? this.files?.communityApp(id, m.sender, m.frame) : this.host.communityApp?.(id, m.sender, m.frame)),
23627
23670
  pair: (m) => this.deliver(id, () => this.host.communityPair?.(id, m.sender, m.payload)),
23628
23671
  changed: () => {
23629
23672
  this.membershipChanged(id);
@@ -23642,7 +23685,11 @@ var Communities = class {
23642
23685
  adminTurn: () => this.host.adminTurn?.(id) ?? Promise.resolve(true),
23643
23686
  relay: (frame) => {
23644
23687
  if (this.live.get(id)?.hub) for (const [, linkId] of this.hearers(id, frame)) this.sendTo(linkId, frame);
23645
- }
23688
+ },
23689
+ ...this.host.linkHandled && { handled: (to) => {
23690
+ const edge = this.host.edges(id).get(to);
23691
+ return edge && this.host.linkHandled ? this.host.linkHandled(edge) : Promise.resolve(false);
23692
+ } }
23646
23693
  });
23647
23694
  this.live.set(id, {
23648
23695
  session,
@@ -24829,6 +24876,8 @@ var Groups = class {
24829
24876
  typings;
24830
24877
  /** Edge signaling through members (WISP 902 · Group Mesh § Signaling through members). */
24831
24878
  signals;
24879
+ /** Files and voice messages in every group, private or community (WISP 503 · Group Files). */
24880
+ files;
24832
24881
  constructor(host, store = db, timings = ENTRY_TIMINGS, communityTimings = COMMUNITY_TIMINGS, random, hubTimings = MESH_HUB_TIMINGS) {
24833
24882
  this.host = host;
24834
24883
  this.store = store;
@@ -24846,6 +24895,27 @@ var Groups = class {
24846
24895
  stored: (id) => this.stored.get(id),
24847
24896
  now: () => this.now()
24848
24897
  });
24898
+ const files = host.groupFiles;
24899
+ if (files) {
24900
+ this.files = new GroupFileDesk({
24901
+ ...files,
24902
+ send: (linkId, frame) => {
24903
+ try {
24904
+ host.sendOnLink(linkId, frame);
24905
+ return true;
24906
+ } catch {
24907
+ return false;
24908
+ }
24909
+ },
24910
+ edges: (groupId) => host.edges(groupId),
24911
+ ready: (linkId) => host.linkReady(linkId, 6),
24912
+ membership: (groupId) => this.fileMembership(groupId),
24913
+ sendApp: (groupId, frame) => this.communities.sendApp(groupId, frame),
24914
+ changed: () => host.emit(),
24915
+ now: () => this.now()
24916
+ });
24917
+ this.communities.files = this.files;
24918
+ }
24849
24919
  }
24850
24920
  async load() {
24851
24921
  const all = await this.store.getGroups();
@@ -24863,6 +24933,7 @@ var Groups = class {
24863
24933
  if (mentionAt) this.lastMentionAt.set(group.id, mentionAt);
24864
24934
  }
24865
24935
  for (const id of this.sessions.keys()) this.reconcileEdges(id);
24936
+ await this.files?.load(all.map((g) => g.id));
24866
24937
  for (const group of this.stored.values()) for (const [, linkId] of this.host.entries(group.id)) if (!group.invitation?.entry || group.invitation.linkId !== linkId) await this.host.closeEdge(linkId);
24867
24938
  }
24868
24939
  /** A group's pin now (WISP 400 § Pinned message), the latest one taken, unpinned or not. */
@@ -25107,6 +25178,58 @@ var Groups = class {
25107
25178
  };
25108
25179
  }
25109
25180
  /**
25181
+ * Announces a file or voice message of mine (WISP 503): `text` is its caption, or the line older apps show
25182
+ * (`groupFileFallback`), `file` its description with the digest of the bytes kept under `fileId`, which this device
25183
+ * then holds and serves. `messageId` is the announcement's.
25184
+ */
25185
+ async sendFile(groupId, text, file, fileId, reply, forwarded) {
25186
+ if (!this.files) return {
25187
+ error: "This app takes no group files",
25188
+ refused: true
25189
+ };
25190
+ const paced = this.files.mayAnnounce(groupId);
25191
+ if (paced) return { error: paced };
25192
+ this.files.outgoing(groupId, fileId, file);
25193
+ let result;
25194
+ if (this.isCommunity(groupId)) result = await this.communities.send(groupId, text, [], reply, forwarded, void 0, file);
25195
+ else {
25196
+ const session = this.sessions.get(groupId);
25197
+ const sent = session ? await session.sendText(text, Date.now(), [], reply, forwarded, void 0, file) : { error: "You are not in this group yet" };
25198
+ if (session) this.typings.say(session, false);
25199
+ result = "error" in sent ? { error: sent.error } : {
25200
+ error: null,
25201
+ messageId: sent.id
25202
+ };
25203
+ }
25204
+ if (result.error) this.files.notSent(groupId, file);
25205
+ return result;
25206
+ }
25207
+ /** The person asks for a group file this device did not fetch by itself, or asks again for one that stopped coming. */
25208
+ downloadFile(fileId) {
25209
+ if (!groupOfFile(fileId, [...this.stored.keys(), ...this.communities.ids()]) || !this.files) throw new Error("No such file");
25210
+ return this.files.download(fileId);
25211
+ }
25212
+ /** An edge's files/3 session opened or closed (WISP 503: the bytes of a group's files go over it). */
25213
+ fileSession(groupId, peerKey, linkId, open) {
25214
+ this.files?.session(groupId, peerKey, linkId, open);
25215
+ }
25216
+ /** A files/3 frame on an edge, from the member it is pinned to. */
25217
+ filesFrame(groupId, peerKey, linkId, frame) {
25218
+ return this.files?.filesFrame(groupId, peerKey, linkId, frame) ?? Promise.resolve();
25219
+ }
25220
+ /** Me in a group I am active in, for its files: who is in it, and who could read one of its messages. */
25221
+ fileMembership(groupId) {
25222
+ if (this.isCommunity(groupId)) return this.communities.fileMembership(groupId);
25223
+ const session = this.sessions.get(groupId);
25224
+ if (session?.status !== "active") return void 0;
25225
+ return {
25226
+ me: session.myKey,
25227
+ community: false,
25228
+ inRoster: (key) => rosterHas(session.roster, key),
25229
+ couldRead: (key, messageId) => session.couldRead(key, messageId)
25230
+ };
25231
+ }
25232
+ /**
25110
25233
  * This side is typing in a private group (with what it is doing), or stopped (WISP 902 · Group Mesh § Typing).
25111
25234
  * Nothing for a community: it does not carry typing yet.
25112
25235
  */
@@ -25347,7 +25470,11 @@ var Groups = class {
25347
25470
  await this.session(groupId).rename(name);
25348
25471
  }
25349
25472
  async forget(groupId) {
25350
- if (this.isCommunity(groupId)) return this.communities.forget(groupId);
25473
+ if (this.isCommunity(groupId)) {
25474
+ await this.communities.forget(groupId);
25475
+ await this.files?.drop(groupId);
25476
+ return;
25477
+ }
25351
25478
  if (this.sessions.get(groupId)?.status === "active") try {
25352
25479
  await this.leave(groupId);
25353
25480
  } catch {}
@@ -25374,6 +25501,7 @@ var Groups = class {
25374
25501
  this.signals.forget(groupId);
25375
25502
  for (const linkId of [...this.host.edges(groupId).values(), ...this.host.entries(groupId).values()]) await this.host.closeEdge(linkId);
25376
25503
  await this.store.deleteGroup(groupId);
25504
+ await this.files?.drop(groupId);
25377
25505
  this.host.historyGone?.(groupId);
25378
25506
  this.host.membersChanged?.(groupId);
25379
25507
  this.host.emit();
@@ -25420,8 +25548,7 @@ var Groups = class {
25420
25548
  */
25421
25549
  async joinByLink(code) {
25422
25550
  if (decodeCommunityLink(code)) {
25423
- const g = decodeCommunityLink(code).g;
25424
- if (this.stored.has(g)) await this.forget(g);
25551
+ if (this.stored.has(decodeCommunityLink(code).g)) throw new Error("This link names a private group of yours, not a community");
25425
25552
  return this.communities.joinByLink(code);
25426
25553
  }
25427
25554
  const link = decodeGroupEntryLink(code);
@@ -25499,6 +25626,7 @@ var Groups = class {
25499
25626
  this.meshTick(now);
25500
25627
  await this.communities.tick(now);
25501
25628
  await this.hubsTick(now);
25629
+ this.files?.tick();
25502
25630
  } finally {
25503
25631
  this.ticking = false;
25504
25632
  }
@@ -25872,6 +26000,12 @@ var Groups = class {
25872
26000
  }
25873
26001
  /** A `group-*` frame on an edge: from the member the edge is pinned to. */
25874
26002
  async handleEdgeFrame(groupId, peerKey, frame) {
26003
+ const t = frame && typeof frame === "object" ? frame.t : void 0;
26004
+ if (t === "group-want" || t === "group-want-no" || t === "group-have") {
26005
+ const linkId = this.host.edges(groupId).get(peerKey);
26006
+ if (linkId && !this.stored.get(groupId)?.left) await this.files?.frame(groupId, peerKey, linkId, frame);
26007
+ return;
26008
+ }
25875
26009
  if (this.isCommunity(groupId)) return this.communities.handleEdgeFrame(groupId, peerKey, frame);
25876
26010
  if (this.stored.get(groupId)?.left) {
25877
26011
  if (this.removesMe(groupId, peerKey, frame)) await this.leaveConfirmed(groupId);
@@ -25900,7 +26034,6 @@ var Groups = class {
25900
26034
  return;
25901
26035
  }
25902
26036
  if (!session) return;
25903
- const t = frame && typeof frame === "object" ? frame.t : void 0;
25904
26037
  if (t === "group-react" || t === "group-reacted") {
25905
26038
  await this.reaction(groupId, session, peerKey, frame);
25906
26039
  return;
@@ -25961,6 +26094,7 @@ var Groups = class {
25961
26094
  edgeReady(groupId, peerKey, linkId) {
25962
26095
  if (this.isCommunity(groupId)) {
25963
26096
  this.communities.edgeReady(groupId, peerKey, linkId);
26097
+ this.files?.edgeReady(groupId, peerKey, linkId);
25964
26098
  return;
25965
26099
  }
25966
26100
  const left = this.stored.get(groupId)?.left;
@@ -26000,6 +26134,7 @@ var Groups = class {
26000
26134
  this.welcomed.get(groupId)?.delete(peerKey);
26001
26135
  this.announceHere(groupId, session, peerKey);
26002
26136
  this.signals.edgeReady(groupId, session, peerKey, linkId);
26137
+ this.files?.edgeReady(groupId, peerKey, linkId);
26003
26138
  this.host.edgeUp?.(groupId, peerKey);
26004
26139
  this.host.emit();
26005
26140
  }
@@ -26201,10 +26336,15 @@ var Groups = class {
26201
26336
  message: async (m) => {
26202
26337
  const timestamp = m.timestamp;
26203
26338
  const mentioned = m.sender !== session.myKey && mentionsMember(m.mentions, session.myKey);
26339
+ const file = m.file && this.files ? await this.files.announced(state.id, {
26340
+ id: m.id,
26341
+ sender: m.sender,
26342
+ file: m.file
26343
+ }, session.myKey) : void 0;
26204
26344
  const message = {
26205
26345
  linkId: MESSAGE_LINK(state.id),
26206
26346
  id: m.id,
26207
- text: m.text,
26347
+ text: file ? groupFileText(m.text, m.file) : m.text,
26208
26348
  sender: m.sender === session.myKey ? "me" : "peer",
26209
26349
  member: m.sender,
26210
26350
  timestamp,
@@ -26212,7 +26352,8 @@ var Groups = class {
26212
26352
  ...mentionFields(m.mentions, mentioned),
26213
26353
  ...m.reply && { replyTo: groupReply(m.reply, session.myKey) },
26214
26354
  ...m.forwarded && { forwarded: m.forwarded },
26215
- ...m.card && { card: m.card }
26355
+ ...m.card && { card: m.card },
26356
+ ...file && { file }
26216
26357
  };
26217
26358
  const stored = m.completes && this.host.completeMessage ? (await this.host.completeMessage(message), false) : await this.host.storeMessage(message);
26218
26359
  const came = m.sender === session.myKey ? timestamp : cameAt(timestamp, stored, arrivalNow(this.now()));
@@ -26229,7 +26370,11 @@ var Groups = class {
26229
26370
  },
26230
26371
  metaChanged: (by, change, at) => {
26231
26372
  this.metaChanged(state.id, session, by, change, at);
26232
- }
26373
+ },
26374
+ ...this.host.linkHandled && { handled: (to) => {
26375
+ const edge = this.host.edges(state.id).get(to);
26376
+ return edge && this.host.linkHandled ? this.host.linkHandled(edge) : Promise.resolve(false);
26377
+ } }
26233
26378
  });
26234
26379
  this.sessions.set(state.id, session);
26235
26380
  this.lastRoster.set(state.id, session.roster);
@@ -26341,16 +26486,34 @@ function edgeView(live, now = Date.now(), noSlot = false) {
26341
26486
  noSlot: true,
26342
26487
  lastSeenAt: Math.max(presenceSeenAt(live.presence), live.lastSyncAt)
26343
26488
  };
26344
- const error = open ? void 0 : live.pairing?.status === "error" ? live.pairing.error : live.discoveryError;
26489
+ const pairingError = live.pairing?.status === "error" ? live.pairing.error : void 0;
26490
+ const error = open ? void 0 : pairingError ?? live.discoveryError;
26345
26491
  const state = open ? "open" : error ? "error" : live.dataLink !== "idle" ? "connecting" : "waiting";
26346
26492
  return {
26347
26493
  linkId: live.stored.id,
26348
26494
  state,
26349
26495
  ...open && live.pairing?.transport ? { transport: live.pairing.transport } : {},
26350
26496
  lastSeenAt: open ? now : Math.max(presenceSeenAt(live.presence), live.lastSyncAt),
26351
- ...error ? { error } : {}
26497
+ ...error ? {
26498
+ error,
26499
+ cause: pairingError ? edgeErrorCause(pairingError) : "relays"
26500
+ } : {}
26352
26501
  };
26353
26502
  }
26503
+ /**
26504
+ * A pairing error the session ends with when nothing is wrong with the member: an attempt dialled again. Their
26505
+ * English is `PairedSession`'s and `GhostLink`'s (packages/core).
26506
+ */
26507
+ var SESSION_DROPS = [
26508
+ /^Session receive queue full$/,
26509
+ /^The peer did not finish authentication\b/,
26510
+ /^Invalid session negotiation$/
26511
+ ];
26512
+ /** What an edge's pairing error comes down to: the relays (its signal could not go out), a session dropped, or else. */
26513
+ function edgeErrorCause(error) {
26514
+ if (error.startsWith("Could not publish connection details:") || /^Could not (publish|read) discovery:/.test(error)) return "relays";
26515
+ return SESSION_DROPS.some((drop) => drop.test(error)) ? "session" : "other";
26516
+ }
26354
26517
  //#endregion
26355
26518
  //#region ../browser/src/engine/groupPayments.ts
26356
26519
  /**
@@ -29230,6 +29393,9 @@ function assertAmount(amount) {
29230
29393
  var asProofLike = (proofs) => proofs;
29231
29394
  var total = (proofs) => proofs.reduce((sum, p) => sum + p.amount, 0);
29232
29395
  var sats = (proofs) => total(proofs.map((p) => ({ amount: p.amount.toNumber() })));
29396
+ var MINT_QUOTE_MISMATCH = "The mint quoted another amount than this invoice's. Nothing was paid.";
29397
+ /** A melt quote that is not for this invoice (`invoice` lower case, as `decodeBolt11` gives it) and its amount. */
29398
+ var quoteMismatch = (quote, invoice, amount) => quote.request.trim().toLowerCase().replace(/^lightning:/, "") !== invoice || quote.amount.toNumber() !== amount;
29233
29399
  var toStored = (mint, p, reserved) => ({
29234
29400
  mint,
29235
29401
  id: p.id,
@@ -29282,6 +29448,8 @@ var CashuWallet = class CashuWallet {
29282
29448
  failedFees = /* @__PURE__ */ new Map();
29283
29449
  /** Melt quotes that ended unpaid with proofs the mint read spent: ecash spent somewhere else, said as such. */
29284
29450
  spentAway = /* @__PURE__ */ new Set();
29451
+ /** Per melt quote shown to the person: the invoice, its amount and the fee reserve the mint quoted. Only these are paid. */
29452
+ shownQuotes = /* @__PURE__ */ new Map();
29285
29453
  /**
29286
29454
  * `getMints`: the mints of one network (the engine's default when none is named), primary first: balances,
29287
29455
  * invoices, payments of that network's Cashu wallet. `getKnownMints`: every mint the user added, whatever its
@@ -30321,7 +30489,10 @@ var CashuWallet = class CashuWallet {
30321
30489
  async quoteInvoice(invoice, network) {
30322
30490
  let lastError = /* @__PURE__ */ new Error("Add a mint in Settings first");
30323
30491
  let mints = this.getMints(network);
30324
- if (decodeBolt11(invoice.trim())?.network === "bitcoin" && mints.some((mint) => mintNetwork(mint) === "testnet")) {
30492
+ const decoded = decodeBolt11(invoice);
30493
+ if (!decoded) throw new Error("That is not an invoice this wallet can pay");
30494
+ if (decoded.amountSat === null) throw new Error("Invoices without an amount are not supported");
30495
+ if (decoded.network === "bitcoin" && mints.some((mint) => mintNetwork(mint) === "testnet")) {
30325
30496
  mints = mints.filter((mint) => mintNetwork(mint) === "mainnet" || fakesLightning(mint));
30326
30497
  if (!mints.length) throw new Error(BITCOIN_INVOICE_ON_TESTNET);
30327
30498
  }
@@ -30329,10 +30500,16 @@ var CashuWallet = class CashuWallet {
30329
30500
  const quote = await (await this.wallet(mint)).createMeltQuoteBolt11(invoice.trim());
30330
30501
  const amount = quote.amount.toNumber();
30331
30502
  const feeReserve = quote.fee_reserve.toNumber();
30503
+ if (quoteMismatch(quote, decoded.invoice, decoded.amountSat)) throw new Error(MINT_QUOTE_MISMATCH);
30332
30504
  if (await this.balanceAt(mint) < amount + feeReserve) {
30333
30505
  lastError = engineError("notEnoughSats");
30334
30506
  continue;
30335
30507
  }
30508
+ this.shownQuotes.set(quote.quote, {
30509
+ request: decoded.invoice,
30510
+ amount,
30511
+ feeReserve
30512
+ });
30336
30513
  return {
30337
30514
  quote: quote.quote,
30338
30515
  mint,
@@ -30364,6 +30541,8 @@ var CashuWallet = class CashuWallet {
30364
30541
  const wallet = await this.wallet(mint);
30365
30542
  const quote = await wallet.checkMeltQuoteBolt11(quoteId);
30366
30543
  if ((await wrap((await store(STORES$1.melts, "readonly")).getAll())).some((m) => m.quote === quote.quote || m.request === quote.request)) throw new Error("This invoice is already being paid");
30544
+ const shown = this.shownQuotes.get(quoteId);
30545
+ if (!shown || quote.quote !== quoteId || quoteMismatch(quote, shown.request, shown.amount) || quote.fee_reserve.toNumber() > shown.feeReserve) throw new Error(MINT_QUOTE_MISMATCH);
30367
30546
  const needed = quote.amount.toNumber() + quote.fee_reserve.toNumber();
30368
30547
  const { inputs, keep, send, swap } = await this.split(mint, needed, await this.proofsAt(mint));
30369
30548
  let preview;
@@ -30398,6 +30577,7 @@ var CashuWallet = class CashuWallet {
30398
30577
  stores[STORES$1.melts].put(melt);
30399
30578
  if (swap) stores[STORES$1.swaps].delete(swap.id);
30400
30579
  });
30580
+ this.shownQuotes.delete(quoteId);
30401
30581
  let result;
30402
30582
  try {
30403
30583
  result = await wallet.completeMelt(preview);
@@ -31197,7 +31377,7 @@ var PaymentDesk = class {
31197
31377
  if (request.target) throw new Error("Review and explicitly approve this payment before sending");
31198
31378
  if (request.state !== "pending") throw new Error("This request is no longer open");
31199
31379
  if (request.lightningPending) throw new Error("A Lightning payment for this request is still pending");
31200
- if ([...this.payments.values()].some((p) => p.kind === "payment" && p.direction === "out" && p.requestId === request.id && p.state !== "reclaimed" && p.state !== "failed")) throw new Error("You already paid this request");
31380
+ if ([...this.payments.values()].some((p) => p.kind === "payment" && p.direction === "out" && p.requestId === request.id && p.state !== "reclaimed" && (p.state !== "failed" || !!p.token))) throw new Error("You already paid this request");
31201
31381
  assertConfirmedReal(network, params.confirmedReal);
31202
31382
  const link = this.requireLink(params.linkId);
31203
31383
  await link.requirePaymentSupport();
@@ -32446,8 +32626,7 @@ var PaymentDesk = class {
32446
32626
  for (const request of [...this.payments.values()]) {
32447
32627
  if (request.kind !== "request" || request.direction !== "out" || request.state !== "pending" || request.target?.method !== "bitcoin") continue;
32448
32628
  const claimed = new Set([...this.payments.values()].filter((p) => p.target?.method === "bitcoin" && p.kind === "request" && p.id !== request.id && p.txid).map((p) => p.txid));
32449
- const receipt = [...this.payments.values()].find((p) => p.kind === "payment" && p.direction === "in" && p.requestId === request.id && p.linkId === request.linkId);
32450
- const seen = await this.bitcoin[walletNetworkOf(request.target.network)].received(request.target, request.amount, claimed, receipt?.txid).catch(() => void 0);
32629
+ const seen = await this.bitcoin[walletNetworkOf(request.target.network)].received(request.target, request.amount, claimed).catch(() => void 0);
32451
32630
  if (!seen || seen.confirmations < 1) continue;
32452
32631
  await this.settleRequest(request, { txid: seen.txid });
32453
32632
  for (const payment of this.payments.values()) if (payment.kind === "payment" && payment.direction === "in" && payment.requestId === request.id && payment.state === "pending") await this.save({
@@ -33441,6 +33620,12 @@ var DEFAULT_SETTINGS = {
33441
33620
  mints: [],
33442
33621
  mintsInitialized: false
33443
33622
  };
33623
+ /**
33624
+ * A card that went on the DHT floor or into a hold goes again live (`restoreCards`) only for the newest this many
33625
+ * messages of a chat, sent within this long: an old chat's cards are not worth a burst of edits.
33626
+ */
33627
+ var CARD_RESTORE_MOST = 50;
33628
+ var CARD_RESTORE_MS = 6048e5;
33444
33629
  /** A reaction said on the live session and not confirmed is said again after this long. */
33445
33630
  var REACTION_RESEND_MS = 3e4;
33446
33631
  function emptyLinkFiles() {
@@ -34407,6 +34592,7 @@ var GhostlyNode = class GhostlyNode {
34407
34592
  live.link.sendGroupFrame(frame);
34408
34593
  },
34409
34594
  linkReady: (linkId, version = 1) => !!this.links.get(linkId)?.link?.supportsGroupVersion(version),
34595
+ linkHandled: (linkId) => this.links.get(linkId)?.link?.handled() ?? Promise.resolve(false),
34410
34596
  linkOpen: (linkId) => !!this.links.get(linkId)?.link?.isDataLinkOpen,
34411
34597
  myNick: () => this.sharedNick,
34412
34598
  seqFloor: () => this.counterFloor,
@@ -34482,7 +34668,17 @@ var GhostlyNode = class GhostlyNode {
34482
34668
  this.groupEdits.resend(groupId, peer).catch(() => {});
34483
34669
  this.sendGroupPinFrame(groupId, peer);
34484
34670
  },
34485
- communityPair: (groupId, sender, payload) => this.communityPay.receivePair(groupId, sender, payload)
34671
+ communityPair: (groupId, sender, payload) => this.communityPay.receivePair(groupId, sender, payload),
34672
+ groupFiles: {
34673
+ store: storedGroupFiles,
34674
+ transfers: this.transfers,
34675
+ settings: () => ({
34676
+ autoDownloads: this.settings.autoDownloads !== false,
34677
+ serveFiles: this.settings.serveFiles !== false
34678
+ }),
34679
+ sendFiles: (linkId, frame) => this.links.get(linkId)?.link?.sendFilesFrame(frame) ?? false,
34680
+ writable: (linkId) => this.links.get(linkId)?.link?.filesWritable()
34681
+ }
34486
34682
  });
34487
34683
  /**
34488
34684
  * Payments in community groups (WISP 903 · Group Community § Payments): the desk pays and requests over a link
@@ -35531,7 +35727,7 @@ var GhostlyNode = class GhostlyNode {
35531
35727
  async statusCardIndex() {
35532
35728
  const rows = [];
35533
35729
  for (const m of await db.getCardMessages()) {
35534
- if (m.card?.kind !== "task" && m.card?.kind !== "routine") continue;
35730
+ if (m.card?.kind !== "task" && m.card?.kind !== "routine" && m.card?.kind !== "usage") continue;
35535
35731
  rows.push({
35536
35732
  linkId: m.linkId,
35537
35733
  id: m.id,
@@ -36448,7 +36644,7 @@ var GhostlyNode = class GhostlyNode {
36448
36644
  delivery: "sending",
36449
36645
  ...preview && { preview },
36450
36646
  ...answers,
36451
- ...card?.kind === "buttons" && { buttonsRestore: "due" }
36647
+ ...card && { cardRestore: "due" }
36452
36648
  });
36453
36649
  this.hold.hold(linkId, {
36454
36650
  kind: "text",
@@ -36660,34 +36856,49 @@ var GhostlyNode = class GhostlyNode {
36660
36856
  return !!live?.caps?.peer?.capabilities.includes(EDIT_CAPABILITY);
36661
36857
  }
36662
36858
  /**
36663
- * A question of mine with buttons went on the DHT floor or into a hold, which carry its text alone (WISP 406 · Message
36664
- * Buttons): its buttons are due to go again live. Once: a row that had them restored already stays so.
36859
+ * A card of mine went on the DHT floor or into a hold, which carry its text alone (WISP 405 · Status Cards, WISP 406 ·
36860
+ * Message Buttons): the card is due to go again live. Once: a row that had it restored already stays so.
36665
36861
  */
36666
- async buttonsWentBare(linkId, message) {
36667
- if (message.card?.kind !== "buttons" || message.buttonsRestore) return;
36668
- await db.patchMessage(linkId, message.id, (current) => current.sender === "me" && current.card?.kind === "buttons" && !current.buttonsRestore ? { buttonsRestore: "due" } : null);
36862
+ async cardWentBare(linkId, message) {
36863
+ if (!message.card || message.cardRestore || message.buttonsRestore) return;
36864
+ await db.patchMessage(linkId, message.id, (current) => current.sender === "me" && current.card && !current.cardRestore && !current.buttonsRestore ? { cardRestore: "due" } : null);
36865
+ }
36866
+ /** The session capability a contact's app lists when it shows a card of this kind, so a restore edit reads as a card there. */
36867
+ static cardCapability(card) {
36868
+ return card.kind === "buttons" ? BUTTONS_CAPABILITY : card.kind === "app" ? APPS_CAPABILITY : STATUS_CARD_CAPABILITY;
36669
36869
  }
36670
36870
  /**
36671
- * Buttons the contact may not have (WISP 406 · Message Buttons): a question whose text went on the DHT floor or into a
36672
- * hold reached the contact as text alone, and a copy under the same id that comes live later is taken as the one
36673
- * already there. Once the chat is live with an app that shows buttons (`buttons/1`) and takes edits, each such
36674
- * question goes again as an edit of its buttons alone: the same text, so no version and no edit mark. Once per
36675
- * message (the row says so, across restarts); the edit queue carries it (a card's edit goes only live, and a later
36676
- * edit of the bot's replaces it, the highest number winning). An app without `buttons/1` (1.0.0 would mark it edited)
36677
- * gets none: the row waits for one that shows buttons. The edit is marked `restore`: the bot's own event stream says
36678
- * nothing of it, while its number counts, so a later edit of the bot's takes the next one.
36871
+ * Cards the contact may not have (WISP 405 · Status Cards, WISP 406 · Message Buttons): a message whose text went on
36872
+ * the DHT floor or into a hold reached the contact as text alone, and a copy under the same id that comes live later
36873
+ * is taken as the one already there. An app card or a finished task gets no later update to carry its card. So once
36874
+ * the chat is live with an app that shows the kind (`buttons/1`, `status-card/1`, `apps/1`) and takes edits, each such
36875
+ * message goes again as an edit of its card alone: the same text, so no version and no edit mark. Once per message (the
36876
+ * row says so, across restarts); the edit queue carries it (a card's edit goes only live, and a later edit of the
36877
+ * author's replaces it, the highest number winning). A contact whose app does not show the kind (it would drop the card
36878
+ * and mark the text edited) gets none: the row waits for one that does. Bounded: the newest `CARD_RESTORE_MOST` such
36879
+ * messages of the last `CARD_RESTORE_MS`; older ones are given up. The edit is marked `restore`: the bot's own event
36880
+ * stream says nothing of it, while its number counts, so a later edit of the bot's takes the next one.
36679
36881
  */
36680
- async restoreButtons(linkId) {
36882
+ async restoreCards(linkId) {
36681
36883
  const live = this.links.get(linkId), link = live?.link;
36682
- if (!live?.stored.profile || live.stored.group || !link?.supportsEdits || !link.sessionOffers.peer?.includes("buttons/1")) return;
36683
- const due = (await db.getMessages(linkId)).filter((m) => m.sender === "me" && m.buttonsRestore === "due");
36884
+ const offers = link?.sessionOffers.peer;
36885
+ if (!live?.stored.profile || live.stored.group || !link?.supportsEdits || !offers) return;
36886
+ const due = (await db.getMessages(linkId)).filter((m) => m.sender === "me" && (m.cardRestore ?? m.buttonsRestore) === "due").sort((a, b) => b.timestamp - a.timestamp);
36887
+ const since = Date.now() - CARD_RESTORE_MS;
36684
36888
  const changed = [];
36685
- for (const message of due) {
36889
+ for (const [index, message] of due.entries()) {
36890
+ const stale = index >= CARD_RESTORE_MOST || message.timestamp < since;
36891
+ if (!stale && message.card && !offers.includes(GhostlyNode.cardCapability(message.card))) continue;
36686
36892
  const at = Date.now();
36687
36893
  if ((await db.patchMessage(linkId, message.id, (current) => {
36688
- if (current.sender !== "me" || current.buttonsRestore !== "due") return null;
36894
+ if (current.sender !== "me" || (current.cardRestore ?? current.buttonsRestore) !== "due") return null;
36895
+ const done = {
36896
+ cardRestore: "sent",
36897
+ buttonsRestore: void 0
36898
+ };
36689
36899
  const seq = (current.edit?.seq ?? 0) + 1;
36690
- if (current.card?.kind !== "buttons" || !current.wireId || seq > STATUS_CARD_LIMITS.edits) return { buttonsRestore: "sent" };
36900
+ if (stale || !current.card || !current.wireId || seq > STATUS_CARD_LIMITS.edits) return done;
36901
+ if (current.edit?.pending) return done;
36691
36902
  return {
36692
36903
  edit: {
36693
36904
  ...withEdit(current, {
@@ -36700,7 +36911,7 @@ var GhostlyNode = class GhostlyNode {
36700
36911
  }).edit,
36701
36912
  restore: true
36702
36913
  },
36703
- buttonsRestore: "sent"
36914
+ ...done
36704
36915
  };
36705
36916
  }))?.edit?.pending) changed.push(message.id);
36706
36917
  }
@@ -36806,7 +37017,7 @@ var GhostlyNode = class GhostlyNode {
36806
37017
  const reply = GhostlyNode.wireReply(message);
36807
37018
  const error = message.card ? await link.sendMessage(message.text, message.timestamp, message.wireId, message.preview, reply, message.forwarded, message.card) : message.forwarded ? await link.sendMessage(message.text, message.timestamp, message.wireId, message.preview, reply, message.forwarded) : reply ? await link.sendMessage(message.text, message.timestamp, message.wireId, message.preview, reply) : await link.sendMessage(message.text, message.timestamp, message.wireId, ...message.preview ? [message.preview] : []);
36808
37019
  await this.noteTextSend(linkId, message, snapshot, at, error, link);
36809
- if (!error && message.via === "pkarr") await this.buttonsWentBare(linkId, message);
37020
+ if (!error && message.via === "pkarr") await this.cardWentBare(linkId, message);
36810
37021
  return error;
36811
37022
  }, (message) => message.via === "pkarr" ? DHT_MESSAGE_TTL : 2e4, (message) => {
36812
37023
  const pending = this.links.get(linkId)?.stored.dhtDeliveryState?.pending;
@@ -36831,7 +37042,7 @@ var GhostlyNode = class GhostlyNode {
36831
37042
  via: "hold",
36832
37043
  resendUntil: void 0
36833
37044
  });
36834
- await this.buttonsWentBare(linkId, message);
37045
+ await this.cardWentBare(linkId, message);
36835
37046
  await this.messagesChanged(linkId, [message.id]);
36836
37047
  this.hold.hold(linkId, {
36837
37048
  kind: "text",
@@ -37264,6 +37475,11 @@ var GhostlyNode = class GhostlyNode {
37264
37475
  return offered;
37265
37476
  }
37266
37477
  fileAction({ linkId, fileId, action }) {
37478
+ if (typeof linkId === "string" && linkId.startsWith("group:")) {
37479
+ if (action !== "accept" && action !== "request") throw new Error("A group's file is downloaded (accept) or asked for again (request)");
37480
+ if (!fileId.startsWith(`group-${linkId.slice(6)}-`)) throw new Error("No such file in this group");
37481
+ return this.groups.downloadFile(fileId);
37482
+ }
37267
37483
  const live = this.links.get(linkId);
37268
37484
  if (!live) throw new Error("No such chat");
37269
37485
  if (![
@@ -37872,6 +38088,58 @@ var GhostlyNode = class GhostlyNode {
37872
38088
  if (!sent.error) this.wakeMentioned(groupId, text, named);
37873
38089
  return sent;
37874
38090
  }
38091
+ /**
38092
+ * Announces a file or voice message in a group (WISP 503 · Group Files): its bytes are in the files store under
38093
+ * `file.id` (`group-<group>-out-…`), put there by the caller as a chat's file is (`sendFile`). The message carries its
38094
+ * description and the SHA-256 of those bytes; its text is `caption`, or the line apps from before show. This device
38095
+ * is its first holder: members fetch it from here, then from each other.
38096
+ */
38097
+ async sendGroupFile({ groupId, file, caption, replyTo, forwarded: hops }) {
38098
+ if (typeof groupId !== "string" || !file || typeof file.id !== "string" || !file.id.startsWith(`group-${groupId}-out-`)) return {
38099
+ error: "Invalid file id",
38100
+ refused: true
38101
+ };
38102
+ if (file.size > GROUP_FILE_LIMITS.maxBytes) return {
38103
+ error: `A group takes files of up to ${formatFileSize(GROUP_FILE_LIMITS.maxBytes)}`,
38104
+ refused: true
38105
+ };
38106
+ const stored = await fileStore.get(file.id);
38107
+ if (!stored?.metadata || stored.linkId !== `group:${groupId}` || stored.direction !== "out") return {
38108
+ error: "The file is gone",
38109
+ refused: true
38110
+ };
38111
+ if (storedSize(stored) !== file.size) return {
38112
+ error: "The file is not all here",
38113
+ refused: true
38114
+ };
38115
+ const digest = stored.digest ?? (stored.blob ? await blobDigest(stored.blob) : await (await fileBytesOf(stored.bytes)).digest(file.id));
38116
+ if (!stored.digest) await fileStore.patch(file.id, { digest });
38117
+ const meta = readGroupFileMeta({
38118
+ name: file.name,
38119
+ mime: file.mime,
38120
+ size: file.size,
38121
+ d: digest,
38122
+ ...file.voice && { voice: file.voice },
38123
+ ...file.video && { video: file.video },
38124
+ ...file.image && { image: file.image }
38125
+ });
38126
+ if (!meta) return {
38127
+ error: "That file cannot go to a group",
38128
+ refused: true
38129
+ };
38130
+ if (file.voice && !meta.voice) return {
38131
+ error: "That recording cannot be sent as a voice message",
38132
+ refused: true
38133
+ };
38134
+ const reply = replyTo === void 0 ? void 0 : await this.replyFor(`group:${groupId}`, replyTo);
38135
+ if (typeof reply === "string") return { error: reply };
38136
+ const text = typeof caption === "string" && caption.trim() ? caption : groupFileFallback(meta);
38137
+ return this.groups.sendFile(groupId, text, meta, file.id, reply && {
38138
+ i: reply.id,
38139
+ s: reply.snippet,
38140
+ f: reply.member
38141
+ }, readForwarded(hops));
38142
+ }
37875
38143
  groupMessages({ groupId }) {
37876
38144
  return this.groups.messages(groupId);
37877
38145
  }
@@ -37986,7 +38254,30 @@ var GhostlyNode = class GhostlyNode {
37986
38254
  continue;
37987
38255
  }
37988
38256
  if (groupId) {
37989
- note("Groups take no files yet");
38257
+ const original = message.file, file = {
38258
+ id: groupFileId(groupId, "out"),
38259
+ name: original.name,
38260
+ size: original.size,
38261
+ mime: original.mime,
38262
+ ...original.voice && { voice: original.voice },
38263
+ ...original.video && { video: original.video },
38264
+ ...original.image && { image: original.image }
38265
+ };
38266
+ await copyForForward(original.id, {
38267
+ linkId: to,
38268
+ wireId: file.id.slice(`group-${groupId}-out-`.length),
38269
+ timestamp: now(),
38270
+ file
38271
+ });
38272
+ const sent = await this.sendGroupFile({
38273
+ groupId,
38274
+ file,
38275
+ forwarded: hops
38276
+ });
38277
+ if (sent.error) {
38278
+ await removeStored(file.id).catch(() => {});
38279
+ note(sent.error);
38280
+ } else if (sent.messageId) result.messageIds.push(sent.messageId);
37990
38281
  continue;
37991
38282
  }
37992
38283
  if (!live.link) {
@@ -39162,7 +39453,7 @@ var GhostlyNode = class GhostlyNode {
39162
39453
  if (params.requestId) {
39163
39454
  if (!request || request.linkId !== params.linkId || request.direction !== "in" || request.state !== "pending" || request.amount !== params.amount) throw new Error("Payment review does not match the authenticated request");
39164
39455
  if (params.target.method !== "cashu" ? JSON.stringify(request.target) !== JSON.stringify(params.target) : !!request.target || params.target.address !== request.id || !request.mints?.includes(params.target.provider)) throw new Error("Selected method or mint does not match the authenticated request");
39165
- if (request.lightningPending || Object.values(this.desk.views()).some((p) => p.kind === "payment" && p.requestId === request.id && p.linkId === params.linkId && !["failed", "reclaimed"].includes(p.state))) throw engineError("requestPaymentInFlight");
39456
+ if (request.lightningPending || this.desk.records().some((p) => p.kind === "payment" && p.requestId === request.id && p.linkId === params.linkId && p.state !== "reclaimed" && (p.state !== "failed" || !!p.token))) throw engineError("requestPaymentInFlight");
39166
39457
  const asked = paymentNetwork(request);
39167
39458
  if (asked !== paying) throw new Error(crossNetwork(paying, asked));
39168
39459
  } else if (params.target.method !== "cashu" || !payee || params.target.address !== payee) throw new Error("Destination does not match the authenticated peer");
@@ -39448,6 +39739,12 @@ var GhostlyNode = class GhostlyNode {
39448
39739
  this.groups.stopTyping();
39449
39740
  }
39450
39741
  }
39742
+ for (const key of ["autoDownloads", "serveFiles"]) {
39743
+ if (settings[key] === void 0) continue;
39744
+ if (settings[key] !== false) delete this.settings[key];
39745
+ else this.settings[key] = false;
39746
+ await db.putSettings(this.settings);
39747
+ }
39451
39748
  if (settings.publicProfiles !== void 0) {
39452
39749
  if (settings.publicProfiles !== false) delete this.settings.publicProfiles;
39453
39750
  else this.settings.publicProfiles = false;
@@ -40455,6 +40752,7 @@ var GhostlyNode = class GhostlyNode {
40455
40752
  arkPaymentsSupport: !entry,
40456
40753
  usdtPaymentsSupport: !entry,
40457
40754
  barkPaymentsSupport: !entry,
40755
+ largeFilesSupport: !entry,
40458
40756
  params: stored,
40459
40757
  rtcAvailable: typeof RTCPeerConnection !== "undefined" && !this.edgeRtcOff.has(linkId),
40460
40758
  resume: !entry && stored.edgeLive ? resumeOn : void 0,
@@ -40531,7 +40829,9 @@ var GhostlyNode = class GhostlyNode {
40531
40829
  onPaymentAsk: (ask) => member() ? this.desk.onPaymentAsk(linkId, ask) : void 0,
40532
40830
  onPayment: (payment) => member() ? this.desk.onPayment(linkId, payment) : void 0,
40533
40831
  onPaymentResult: (result) => member() ? this.desk.onPaymentResult(linkId, result) : void 0,
40534
- onPeerNick: (nick) => this.groups.edgeNick(group, peer, nick ?? void 0)
40832
+ onPeerNick: (nick) => this.groups.edgeNick(group, peer, nick ?? void 0),
40833
+ onFilesFrame: (frame) => member() ? this.groups.filesFrame(group, peer, linkId, frame) : void 0,
40834
+ onFilesSession: (open) => this.groups.fileSession(group, peer, linkId, open)
40535
40835
  },
40536
40836
  onPresence: (presence) => {
40537
40837
  if (presence.online && !seen) {
@@ -40833,7 +41133,7 @@ var GhostlyNode = class GhostlyNode {
40833
41133
  onMessageEdit: stored.profile && !stored.group ? (edit) => this.receiveEdit(linkId, edit) : void 0,
40834
41134
  onEditReceipt: stored.profile && !stored.group ? (id, e) => this.editsFor(linkId).received(id, e) : void 0,
40835
41135
  onEditSupport: (supported) => {
40836
- if (supported && stored.profile && !stored.group) this.restoreButtons(linkId).catch(() => {}).then(() => this.editsFor(linkId).flush()).catch(() => {});
41136
+ if (supported && stored.profile && !stored.group) this.restoreCards(linkId).catch(() => {}).then(() => this.editsFor(linkId).flush()).catch(() => {});
40837
41137
  },
40838
41138
  onWakeSupport: (supported) => {
40839
41139
  if (supported && stored.profile && !stored.group) this.shareWake(linkId);
@@ -41101,7 +41401,7 @@ var GhostlyNode = class GhostlyNode {
41101
41401
  get nativeFactories() {
41102
41402
  const relay = this.relaysHyperdht ? this.hyperdhtRelay : "";
41103
41403
  return {
41104
- ...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-w9cwVqGZ.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
41404
+ ...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-DvXgebV7.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
41105
41405
  ...this.options.nativeTransports,
41106
41406
  ...this.ownIrohRelays ? { "iroh/1": (seedB64) => this.options.nativeTransports["iroh/1"](seedB64, this.settings.irohRelays?.length ? { relays: [...this.settings.irohRelays] } : {}) } : {},
41107
41407
  ...this.options.irohWeb ? { "iroh/1": (seedB64) => createIrohWebEndpoint(seedB64, { relays: this.irohRelays }) } : {}
@@ -41369,12 +41669,16 @@ var GhostlyNode = class GhostlyNode {
41369
41669
  ...whole.replyTo && { replyTo: whole.replyTo },
41370
41670
  ...whole.forwarded && { forwarded: whole.forwarded },
41371
41671
  ...whole.card && { card: whole.card },
41372
- ...whole.press && { press: whole.press }
41672
+ ...whole.press && { press: whole.press },
41673
+ ...whole.file && {
41674
+ file: whole.file,
41675
+ text: whole.text
41676
+ }
41373
41677
  };
41374
41678
  if (!Object.keys(added).length) return;
41375
41679
  if (!await db.patchMessage(message.linkId, message.id, (stored) => {
41376
41680
  if (stored.member !== message.member || stored.sender !== message.sender) return null;
41377
- const { card: _card, ...rest } = added;
41681
+ const { card: _card, text: _text, ...rest } = added;
41378
41682
  return stored.edit ? rest : added;
41379
41683
  })) return;
41380
41684
  await this.messagesChanged(message.linkId, [message.id]);
@@ -41844,4 +42148,4 @@ function signalKind(signal) {
41844
42148
  //#endregion
41845
42149
  export { EngineServer, hyperdhtRelayProblem as n, server_exports as t };
41846
42150
 
41847
- //# sourceMappingURL=server-TamKsfmC.js.map
42151
+ //# sourceMappingURL=server-GJW_5wGI.js.map