@ghostlytools/cli 1.2.0 → 1.2.1

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,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { $a as readAppRevocations, $i as parseImageMeta, $n as engineError, $o as handoffContextProof, $r as pinNumberHolds, $s as HANDOFF_CAPABILITY, $t as PaymentPreflightError, A as decodeGroupEntryLink, Aa as pairedReplyAuthor, Ac as withRequestOptions, Ai as IDENTITY_REVOCATION_LABEL, Ar as mergeBeacon, B as groupWakes, Ba as statusCardText, Bc as publicKeyFromZ32, Bi as lastSharedWithMe, Bo as HANDOFF_OFFER, Br as FILE_LIMITS, Bs as readEnrollHello, C as COMMUNITY_LIMITS, Ca as nextReactionNumber, Cc as RelayTransport, Ci as readManifest, Co as RTC_CONFIG, Cr as COMMUNITY_TOPOLOGY, Cs as readSetUpdate, D as decodeCommunityLink, Da as readReaction, Dc as traceLink, Di as dialDescriptors, Dr as freshHubs, Ds as verifySetUpdate, E as communityHasFrame, Ea as reactionIsNewer, Ei as capsDescriptors, Er as doorHubs, Es as signTombstone, F as mergeKnocks, Fi as identityRevocationValue, Fo as HANDOFF_ACK, Fr as readBeacon, Fs as enrollDoneFrame, Ga as shownTime, Gc as fromBase64Url, Gi as automaticTransport, Gn as findEndpoint, Go as HANDOFF_VERIFIED, Gr as TypingSender, Gs as OwnInviteError, Gt as didDhtFromPublicKey, Ha as claimedTime, Hi as emptyProofLedger, Hn as ENDPOINT, Ho as HANDOFF_PIECE_BYTES, Hr as transferStalled, Hs as createDeviceInvite, I as readKnocks, Ia as STATUS_CARD_LIMITS, Ii as identityStatement, Io as HANDOFF_CANCEL, Ir as readBeaconHead, Is as enrollGrantFrame, J as carryMentions, Ja as appFingerprint, Jc as toZ32, Jn as parseFedimintRequestPayload, Jo as handoffAttemptFailed, Js as encodeInviteCode, Kc as randomBytes, Kn as isFederationId, Ko as filePartName, Ks as createChatInvite, Kt as encodeDidDhtDeactivated, Lc as createIdentity, Li as newIdentityBinding, Lo as HANDOFF_DONE, Lr as readLobby, Ls as enrollHelloFrame, M as entryParams, Ma as replySnippet, Mi as IdentityExchange, Mo as HTTP_SERVICE_PROTO, Mr as newerHead, Ms as enrollCancelFrame, N as knockIdentity, Na as BUTTON_ID, Nc as createRelayPayload, No as LEGACY_SERVICES, Nr as pickHubs, Ns as enrollCancelReason, O as encodeCommunityLink, Oa as validReactionNumber, Oc as budgetRetryMs, Or as lobbyKeys, P as knockRecords, Pi as emptyIdentityLedger, Po as serviceIdFromName, Pr as rankHubs, Ps as enrollDigits, Qa as isAppRef, Qi as dhtFileId, Qn as pairedMessageFrame, Qo as handoffContext, Qr as pinIsNewer, Qs as ENROLL_CAPABILITY, Qt as ONCHAIN_PROVIDER, R as GroupWakeLimiter, Ra as checkStatusCard, Rc as identityFromSeed, Ri as receivedIdentityStatus, Ro as HANDOFF_HAVE, Rr as shouldBeHub, Rs as enrollProofFrame, S as sealGroupTyping, Sa as ReactionWindow, Sc as DEFAULT_RELAYS, Si as newMailbox, Sr as pickMeshHubs, T as GROUP_READ_NOTE_COMMUNITY, Ta as reactionEmoji, Tc as normalizeRelayUrl, Ti as DHT_TEXT_CAPABILITY, Tr as beaconRecords, Ts as setUpdateFrame, U as GROUP_READ_NOTE, Ua as heardTime, Uc as sanitizeAvatar, Ui as validExternalKey, Un as cashuRequestPayload, Uo as HANDOFF_REQUEST, Us as deviceInviteJoinerParams, V as parseGroupWakeFrame, Va as arrivalKey, Vc as verify, Vi as PeerProofs, Vn as decodeBolt11, Vo as HANDOFF_PAKE, Vr as transferEnded, Vs as verifyEnrollProof, W as GroupSession, Wa as receivedTimestamp, Wc as bytesEqual, Wi as TRANSPORTS, Wn as fedimintRequestPayload, Wo as HANDOFF_TIMINGS, Ws as readDeviceInvite, Wt as didDhtDocument, X as mentionsMember, Xc as utf8Encode, Xi as peekDhtMailbox, Xn as INVITE_TAKEN, Xo as handoffBusyFrame, Xr as mayPin, Xs as inviteOwnership, Xt as ETHEREUM_USDT, Y as parseCommunityEdit, Ya as appRef, Yc as utf8Decode, Yi as DHT_MESSAGE_TTL, Yn as GhostLink, Yo as handoffAttemptSucceeded, Yr as PIN_LIMITS, Yt as signDidDhtPacket, Z as groupName, Za as isAppKey, Zi as DHT_FILE_CAPABILITY, Zn as LIVENESS_PING_MS, Zo as handoffCancelFrame, Zr as nextPinNumber, Zs as DEVICES_CAPABILITY, Zt as EVM_TEST_CHAINS, _ as readAppBundle, _a as forwardedAgain, _c as readTurnPacket, _i as HOLD_CAPABILITY, _o as parseLocalTarget, _r as groupSignalFrame, _s as readHandoffPake, _t as isAtprotoDid, a as planAppUpdate, ac as newDeviceSetSecret, ai as parseWakeFrame, an as assertTokenUnits, as as handoffOfferFrame, b as groupTypingEpoch, ba as GROUP_REACTION_FRAME, bc as turnKeys, bi as HoldKeys, bo as decodeControl, br as mayBeHub, bs as sealHandoffPiece, ca as fileMessageText, ci as checkPushEndpoint, cn as validatePaymentTarget, co as verifyCommitSignature, cr as readGroupFileMeta, cs as handoffRetryAfter, da as parseVideoMeta, dc as classifyTurnRead, di as isAppVersion, dn as SPARK_PROVIDER, do as DHT_POLL_INTERVALS, ds as newHandoffId, ec as deviceEchoFrame, ei as readPin, en as SEPOLIA_TEST_USDT, er as engineText, es as handoffDataFrame, f as appDigest, fa as EDIT_BUFFER_MS, fc as turnPutSummary, fi as APPS_CAPABILITY, fn as sparkAddressKind, fr as CarriedTransport, fs as readHandoffAck, g as isAppUrl, ga as parseLinkPreview, gc as nextTurnPosition, gi as UPGRADE_CAPABILITY, gn as isPubkyKey, go as formatLocalTarget, gs as readHandoffHello, ha as dhtEditId, hc as firstDeviceSetSecret, hi as STATUS_CARD_CAPABILITY, ho as GhostlyHttpError, hs as readHandoffHave, i as checkAppBeforeRun, ia as sanitizeMime, ic as devicePingFrame, ii as newWakeToken, in as WALLET_NETWORKS, io as GROUP_MEMBER_CAP, is as handoffManifestFrames, j as encodeGroupEntryLink, ja as readReply, jc as PacketTooLargeError, jo as emptyLinkRecords, jr as mergeLobby, ka as wireReaction, kc as isDiscoveryBudgetError, ko as parseCallSignal, kr as lobbyRecords, l as APP_BUNDLE_LIMITS, la as parseVoiceMeta, lc as TURN_RAISE_READ_TIMEOUT_MS, li as vapidKeysMatch, ln as walletNetworkOf, lo as edgeParams, ls as handoffStreamKey, ma as RateWindow, mc as TURN_MAX, mi as EDIT_CAPABILITY, mn as isBitcoinAddress, mo as presenceSeenAt, ms as readHandoffDone, n as appStoreDecision, na as safeBlobType, nc as deviceKeyZ32, ni as WakeLimiter, nn as TEST_USDT_FAUCET_AMOUNT, no as canonicalJson, ns as handoffEphemeral, oc as seedSigner, oi as relayRequest, on as assertWholeSats, oo as MEMBER_KEY, or as groupFileFallback, os as handoffPakeFrame, p as appViewOf, pa as EDIT_RATE_WINDOW_MS, pc as TURN_LAST_SEQUENCE, pi as BUTTONS_CAPABILITY, pn as sparkInvoiceDetails, po as RELAY_POLL_INTERVALS, ps as readHandoffCancel, q as GROUP_EDIT_TEXT_BYTES, qc as toBase64Url, qn as parseCashuRequestPayload, qo as handoffAttemptAllowed, qr as typingActivity, qs as decodeInviteCode, qt as encodeDidDhtPacket, r as appUpdateDecision, ra as sanitizeFileName, rc as deviceLinkPairing, ri as deviceWakeRequest, rn as USDT_PUBLIC_RPC, rs as handoffHelloFrame, s as readAppStore, sc as webCryptoSigner, si as wakeRequest, sn as formatPaymentAmount, so as rosterHas, ss as handoffReleaseFrame, t as appRevocationFor, ta as formatFileSize, tc as deviceEchoNonce, ti as WAKE_CALL_INTERVAL_MS, tr as GROUP_FILE_LIMITS, ts as handoffDigest, u as APP_BUNDLE_MAGIC, uc as TURN_SETTLE_MS, ui as chatAppId, un as SPARK_NETWORKS, uo as DirectPathWatch, us as handoffVersions, va as readForwarded, vc as signTurnPacket, vi as HOLD_DATED_BACK_MS, vo as webLocalFetch, vr as readGroupSignal, vs as readHandoffTurnFrame, vt as normalizeAtprotoHandle, w as CommunitySession, wa as queueReaction, wc as currentRelays, wi as CapsExchange, wr as beaconKeys, ws as setAckFrame, x as openGroupTyping, xa as REACTION_LIMITS, xc as turnName, xi as HoldRefusedError, xo as encodeControl, xr as meshHubs, y as GroupTypingBoard, ya as COMMUNITY_REACTION_FRAME, yc as signTurnRelease, yi as HOLD_LIMITS, yo as LIMITS, yr as MESH_HUBS, ys as readHandoffVerified, z as groupWakeFrame, za as sameButtonText, zc as identityFromSeedB64, zi as revokesIdentity, zo as HANDOFF_HELLO, zr as ChatFiles, zs as readEnrollGrant } from "./src-BMUcYQ7b.js";
2
+ import { $a as appRef, $c as randomBytes, $i as parseImageMeta, $n as engineError, $o as filePartName, $r as pinNumberHolds, $s as createChatInvite, $t as PaymentPreflightError, A as decodeGroupEntryLink, Aa as pairedReplyAuthor, Ac as RelayTransport, Ai as IDENTITY_REVOCATION_LABEL, Ao as RTC_CONFIG, Ar as mergeBeacon, As as readSetUpdate, B as groupWakes, Bc as createRelayPayload, Bi as lastSharedWithMe, Bo as LEGACY_SERVICES, Br as FILE_LIMITS, Bs as enrollCancelReason, C as COMMUNITY_LIMITS, Ca as nextReactionNumber, Cc as nextTurnPosition, Ci as readManifest, Co as formatLocalTarget, Cr as COMMUNITY_TOPOLOGY, Cs as readHandoffHello, D as decodeCommunityLink, Da as readReaction, Dc as turnKeys, Di as dialDescriptors, Do as decodeControl, Dr as freshHubs, Ds as sealHandoffPiece, E as communityHasFrame, Ea as reactionIsNewer, Ec as signTurnRelease, Ei as capsDescriptors, Eo as LIMITS, Er as doorHubs, Es as readHandoffVerified, F as mergeKnocks, Fc as budgetRetryMs, Fi as identityRevocationValue, Fr as readBeacon, Ga as arrivalKey, Gc as identityFromSeed, Gi as automaticTransport, Gn as findEndpoint, Go as HANDOFF_HAVE, Gr as TypingSender, Gs as enrollProofFrame, Gt as didDhtFromPublicKey, Ha as checkStatusCard, Hi as emptyProofLedger, Hn as ENDPOINT, Ho as HANDOFF_ACK, Hr as transferStalled, Hs as enrollDoneFrame, I as readKnocks, Ic as isDiscoveryBudgetError, Ii as identityStatement, Io as parseCallSignal, Ir as readBeaconHead, J as carryMentions, Ja as receivedTimestamp, Jc as verify, Jn as parseFedimintRequestPayload, Jo as HANDOFF_PAKE, Js as verifyEnrollProof, Ka as claimedTime, Kc as identityFromSeedB64, Kn as isFederationId, Ko as HANDOFF_HELLO, Ks as readEnrollGrant, Kt as encodeDidDhtDeactivated, Lc as withRequestOptions, Li as newIdentityBinding, Lr as readLobby, M as entryParams, Ma as replySnippet, Mc as normalizeRelayUrl, Mi as IdentityExchange, Mr as newerHead, Ms as setUpdateFrame, N as knockIdentity, Na as BUTTON_ID, Nr as pickHubs, Ns as signTombstone, O as encodeCommunityLink, Oa as validReactionNumber, Oc as turnName, Oo as encodeControl, Or as lobbyKeys, P as knockRecords, Pc as traceLink, Pi as emptyIdentityLedger, Pr as rankHubs, Ps as verifySetUpdate, Qa as appFingerprint, Qc as fromBase64Url, Qi as dhtFileId, Qn as pairedMessageFrame, Qo as HANDOFF_VERIFIED, Qr as pinIsNewer, Qs as OwnInviteError, Qt as ONCHAIN_PROVIDER, R as GroupWakeLimiter, Ra as STATUS_CARD_LIMITS, Rc as PacketTooLargeError, Ri as receivedIdentityStatus, Ro as emptyLinkRecords, Rr as shouldBeHub, S as sealGroupTyping, Sa as ReactionWindow, Sc as firstDeviceSetSecret, Si as newMailbox, So as GhostlyHttpError, Sr as pickMeshHubs, Ss as readHandoffHave, T as GROUP_READ_NOTE_COMMUNITY, Ta as reactionEmoji, Tc as signTurnPacket, Ti as DHT_TEXT_CAPABILITY, To as webLocalFetch, Tr as beaconRecords, Ts as readHandoffTurnFrame, U as GROUP_READ_NOTE, Ua as sameButtonText, Ui as validExternalKey, Un as cashuRequestPayload, Uo as HANDOFF_CANCEL, Us as enrollGrantFrame, V as parseGroupWakeFrame, Vi as PeerProofs, Vn as decodeBolt11, Vo as serviceIdFromName, Vr as transferEnded, Vs as enrollDigits, W as GroupSession, Wa as statusCardText, Wc as createIdentity, Wi as TRANSPORTS, Wn as fedimintRequestPayload, Wo as HANDOFF_DONE, Ws as enrollHelloFrame, Wt as didDhtDocument, X as mentionsMember, Xc as sanitizeAvatar, Xi as peekDhtMailbox, Xn as INVITE_TAKEN, Xo as HANDOFF_REQUEST, Xr as mayPin, Xs as deviceInviteJoinerParams, Xt as ETHEREUM_USDT, Y as parseCommunityEdit, Ya as shownTime, Yi as DHT_MESSAGE_TTL, Yn as GhostLink, Yo as HANDOFF_PIECE_BYTES, Yr as PIN_LIMITS, Ys as createDeviceInvite, Yt as signDidDhtPacket, Z as groupName, Za as APP_STATEMENT_LIMITS, Zc as bytesEqual, Zi as DHT_FILE_CAPABILITY, Zn as LIVENESS_PING_MS, Zo as HANDOFF_TIMINGS, Zr as nextPinNumber, Zs as readDeviceInvite, Zt as EVM_TEST_CHAINS, _ as readAppBundle, _a as forwardedAgain, _c as TURN_SETTLE_MS, _i as HOLD_CAPABILITY, _o as DirectPathWatch, _r as groupSignalFrame, _s as handoffVersions, _t as isAtprotoDid, a as planAppUpdate, ac as ENROLL_CAPABILITY, ai as parseWakeFrame, an as assertTokenUnits, as as handoffContext, b as groupTypingEpoch, ba as GROUP_REACTION_FRAME, bc as TURN_LAST_SEQUENCE, bi as HoldKeys, bo as RELAY_POLL_INTERVALS, br as mayBeHub, bs as readHandoffCancel, ca as fileMessageText, cc as deviceEchoNonce, ci as checkPushEndpoint, cn as validatePaymentTarget, cr as readGroupFileMeta, cs as handoffDigest, da as parseVideoMeta, dc as devicePingFrame, di as isAppVersion, dn as SPARK_PROVIDER, ds as handoffManifestFrames, ec as decodeInviteCode, ei as readPin, el as toBase64Url, en as SEPOLIA_TEST_USDT, er as engineText, es as handoffAttemptAllowed, f as appDigest, fa as EDIT_BUFFER_MS, fc as newDeviceSetSecret, fi as APPS_CAPABILITY, fn as sparkAddressKind, fo as MEMBER_KEY, fr as CarriedTransport, fs as handoffOfferFrame, g as isAppUrl, ga as parseLinkPreview, gc as TURN_RAISE_READ_TIMEOUT_MS, gi as UPGRADE_CAPABILITY, gn as isPubkyKey, go as edgeParams, gs as handoffStreamKey, ha as dhtEditId, hi as STATUS_CARD_CAPABILITY, ho as verifyCommitSignature, hs as handoffRetryAfter, i as checkAppBeforeRun, ia as sanitizeMime, ic as DEVICES_CAPABILITY, ii as newWakeToken, in as WALLET_NETWORKS, is as handoffCancelFrame, j as encodeGroupEntryLink, ja as readReply, jc as currentRelays, jr as mergeLobby, js as setAckFrame, ka as wireReaction, kc as DEFAULT_RELAYS, kr as lobbyRecords, l as APP_BUNDLE_LIMITS, la as parseVoiceMeta, lc as deviceKeyZ32, li as vapidKeysMatch, ln as walletNetworkOf, lo as readCanonicalJson, ls as handoffEphemeral, ma as RateWindow, mc as webCryptoSigner, mi as EDIT_CAPABILITY, mn as isBitcoinAddress, mo as rosterHas, ms as handoffReleaseFrame, n as appStoreDecision, na as safeBlobType, ni as WakeLimiter, nl as utf8Decode, nn as TEST_USDT_FAUCET_AMOUNT, no as isAppRef, ns as handoffAttemptSucceeded, oc as HANDOFF_CAPABILITY, oi as relayRequest, on as assertWholeSats, or as groupFileFallback, os as handoffContextProof, p as appViewOf, pa as EDIT_RATE_WINDOW_MS, pc as seedSigner, pi as BUTTONS_CAPABILITY, pn as sparkInvoiceDetails, po as commitHash, ps as handoffPakeFrame, q as GROUP_EDIT_TEXT_BYTES, qa as heardTime, qc as publicKeyFromZ32, qn as parseCashuRequestPayload, qo as HANDOFF_OFFER, qr as typingActivity, qs as readEnrollHello, qt as encodeDidDhtPacket, r as appUpdateDecision, ra as sanitizeFileName, rc as inviteOwnership, ri as deviceWakeRequest, rl as utf8Encode, rn as USDT_PUBLIC_RPC, ro as readAppRevocation, rs as handoffBusyFrame, s as readAppStore, sc as deviceEchoFrame, si as wakeRequest, sn as formatPaymentAmount, so as canonicalJson, ss as handoffDataFrame, t as appRevocationFor, ta as formatFileSize, tc as encodeInviteCode, ti as WAKE_CALL_INTERVAL_MS, tl as toZ32, to as isAppKey, tr as GROUP_FILE_LIMITS, ts as handoffAttemptFailed, u as APP_BUNDLE_MAGIC, uc as deviceLinkPairing, ui as chatAppId, un as SPARK_NETWORKS, uo as GROUP_MEMBER_CAP, us as handoffHelloFrame, va as readForwarded, vc as classifyTurnRead, vi as HOLD_DATED_BACK_MS, vo as DHT_POLL_INTERVALS, vr as readGroupSignal, vs as newHandoffId, vt as normalizeAtprotoHandle, w as CommunitySession, wa as queueReaction, wc as readTurnPacket, wi as CapsExchange, wo as parseLocalTarget, wr as beaconKeys, ws as readHandoffPake, x as openGroupTyping, xa as REACTION_LIMITS, xc as TURN_MAX, xi as HoldRefusedError, xo as presenceSeenAt, xr as meshHubs, xs as readHandoffDone, y as GroupTypingBoard, ya as COMMUNITY_REACTION_FRAME, yc as turnPutSummary, yi as HOLD_LIMITS, yr as MESH_HUBS, ys as readHandoffAck, z as groupWakeFrame, zi as revokesIdentity, zo as HTTP_SERVICE_PROTO, zr as ChatFiles, zs as enrollCancelFrame } from "./src-aKGjb_yz.js";
3
3
  import { a as clearProfileStores, c as fileStore, d as profileOpenFailure, f as store, i as STORES$1, l as newestWalletTx, m as wrap, n as APP_STORAGE_SCOPE_INDEX, o as databaseName, p as transact, r as CARD_INDEX, s as deleteDatabase, t as APP_STORAGE_CHAT_INDEX, u as openDb } from "./idb-CTF6LF5B.js";
4
4
  import { a as fileBytesOf, c as removeFileBytes, i as fileBytes, l as FILE_BYTES_STEP, n as blobDigest } from "./fileBytes-BcYZhNgF.js";
5
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, 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, 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";
@@ -180,7 +180,7 @@ async function migrateWalletNetworks(now = Date.now()) {
180
180
  //#endregion
181
181
  //#region ../browser/src/engine/paymentAdapters/usdtWallet.ts
182
182
  /** The Ethereum libraries, a large part of the app: loaded once a USDT wallet is made, opened or restored, not with the engine. */
183
- var loadUsdt = () => import("./usdt-DklGBSeq.js");
183
+ var loadUsdt = () => import("./usdt-CUfucwZf.js");
184
184
  /** Every new profile starts with this wallet: an address to receive on, nothing to set up. */
185
185
  var DEFAULT_USDT = {
186
186
  network: "ethereum",
@@ -642,7 +642,7 @@ async function restoreArkDatabase(walletId, snapshot) {
642
642
  //#endregion
643
643
  //#region ../browser/src/engine/paymentAdapters/arkWallet.ts
644
644
  /** The Ark SDK, a large part of the app: loaded once an Ark wallet is made, opened or restored, not with the engine. */
645
- var loadArkade = () => import("./arkade-3szZ1IpP.js");
645
+ var loadArkade = () => import("./arkade-CtCpFaF7.js");
646
646
  /** Every new profile starts with this wallet: nothing to set up before receiving. */
647
647
  var DEFAULT_ARK = {
648
648
  network: "bitcoin",
@@ -11143,6 +11143,10 @@ var APP_CHECK_TIMINGS = {
11143
11143
  /** A check at start is skipped when every app was checked this recently (a page reloaded several times). */
11144
11144
  freshMs: 36e5
11145
11145
  };
11146
+ /** Revocations are checked this long at a time before the page's thread is let go. */
11147
+ var REVOCATIONS_SLICE_MS = 10;
11148
+ /** `ghostly-revoke.json` files whose result is kept, so an unchanged one is not checked again (the oldest dropped first). */
11149
+ var REVOCATION_FILES_KEPT = 64;
11146
11150
  /** A fetched bundle kept for its install screen: at most this many, for this long. */
11147
11151
  var STAGED = {
11148
11152
  max: 4,
@@ -11241,11 +11245,15 @@ var Apps = class {
11241
11245
  /** Runs the person started with Run anyway (`<ref> <digest>`), for this engine's life: a removal does not cut them off. */
11242
11246
  anyway = /* @__PURE__ */ new Set();
11243
11247
  views = null;
11248
+ held = /* @__PURE__ */ new Map();
11249
+ heldStores = null;
11244
11250
  /**
11245
11251
  * Listing URLs whose peek showed nothing to take (`<store> <ref> <url>` → `<index digest> <listed digest>`): the same
11246
11252
  * index listing the same is not peeked again at every check.
11247
11253
  */
11248
11254
  listingMisses = /* @__PURE__ */ new Map();
11255
+ /** `ghostly-revoke.json` files checked, by the SHA-256 of their bytes: their revocations, or null when refused. */
11256
+ revocationFiles = /* @__PURE__ */ new Map();
11249
11257
  stopped = false;
11250
11258
  constructor(host) {
11251
11259
  this.host = host;
@@ -11269,6 +11277,9 @@ var Apps = class {
11269
11277
  if (this.sweep) clearTimeout(this.sweep);
11270
11278
  this.sweep = null;
11271
11279
  this.staged.clear();
11280
+ this.held.clear();
11281
+ this.heldStores = null;
11282
+ this.views = null;
11272
11283
  }
11273
11284
  schedule(ms, atStart = false) {
11274
11285
  if (this.timer) clearTimeout(this.timer);
@@ -11300,8 +11311,22 @@ var Apps = class {
11300
11311
  async installed() {
11301
11312
  return wrap((await store(STORES$1.apps, "readonly")).getAll());
11302
11313
  }
11303
- async app(ref) {
11304
- return wrap((await store(STORES$1.apps, "readonly")).get(ref));
11314
+ /**
11315
+ * An installed app's record, read once and again after it is written here (the only writer, as for `heldViews`). An
11316
+ * app's every storage call, file read and chat frame asks for it. A reference not installed is not kept.
11317
+ */
11318
+ app(ref) {
11319
+ const kept = this.held.get(ref);
11320
+ if (kept) return kept;
11321
+ const read = store(STORES$1.apps, "readonly").then((s) => wrap(s.get(ref)));
11322
+ this.held.set(ref, read);
11323
+ const drop = () => {
11324
+ if (this.held.get(ref) === read) this.held.delete(ref);
11325
+ };
11326
+ read.then((app) => {
11327
+ if (!app) drop();
11328
+ }, drop);
11329
+ return read;
11305
11330
  }
11306
11331
  async installedOrFail(ref) {
11307
11332
  if (!isAppRef(ref)) fail("bad-ref", "Not an app reference");
@@ -11330,7 +11355,11 @@ var Apps = class {
11330
11355
  if (app) await this.stillRunnable(app);
11331
11356
  }
11332
11357
  async putApp(app) {
11333
- await wrap((await store(STORES$1.apps, "readwrite")).put(app));
11358
+ try {
11359
+ await wrap((await store(STORES$1.apps, "readwrite")).put(app));
11360
+ } finally {
11361
+ this.held.delete(app.ref);
11362
+ }
11334
11363
  }
11335
11364
  /**
11336
11365
  * Writes an app's record back only while that install is still there (the same `installedAt`), read and written in
@@ -11345,33 +11374,58 @@ var Apps = class {
11345
11374
  tx.objectStore(STORES$1.apps).put(app);
11346
11375
  put = true;
11347
11376
  };
11348
- await done(tx);
11377
+ try {
11378
+ await done(tx);
11379
+ } finally {
11380
+ this.held.delete(app.ref);
11381
+ }
11349
11382
  return put;
11350
11383
  }
11384
+ /**
11385
+ * The stores' records by key, read once and kept in step by the writes here (the only writer: pages in other tabs
11386
+ * share this engine). A record holds its store's whole index, up to 4 MiB, and IndexedDB reads and writes it whole:
11387
+ * the Apps page, the update check and an app's every storage call and chat frame ask for them.
11388
+ */
11389
+ records() {
11390
+ return this.heldStores ??= store(STORES$1.appStores, "readonly").then((s) => wrap(s.getAll())).then((all) => new Map(all.map((r) => [r.key, r])), (error) => {
11391
+ this.heldStores = null;
11392
+ throw error;
11393
+ });
11394
+ }
11351
11395
  async storeRecord(key) {
11352
- return wrap((await store(STORES$1.appStores, "readonly")).get(key));
11396
+ return (await this.records()).get(key);
11397
+ }
11398
+ /** A write of the stores' records: what is asked while it runs waits for it, as a read of IndexedDB would; one that fails leaves what was held. */
11399
+ async writeStores(write, apply) {
11400
+ const before = this.records();
11401
+ const written = before.then(async (held) => {
11402
+ await wrap(write(await store(STORES$1.appStores, "readwrite")));
11403
+ apply(held);
11404
+ this.views = null;
11405
+ });
11406
+ const after = written.then(() => before, () => before);
11407
+ this.heldStores = after;
11408
+ after.catch(() => {
11409
+ if (this.heldStores === after) this.heldStores = null;
11410
+ });
11411
+ await written;
11353
11412
  }
11354
- async putStore(record) {
11355
- await wrap((await store(STORES$1.appStores, "readwrite")).put(record));
11356
- this.views = null;
11413
+ putStore(record) {
11414
+ return this.writeStores((records) => records.put(record), (held) => held.set(record.key, record));
11357
11415
  }
11358
- async deleteStore(key) {
11359
- await wrap((await store(STORES$1.appStores, "readwrite")).delete(key));
11360
- this.views = null;
11416
+ deleteStore(key) {
11417
+ return this.writeStores((records) => records.delete(key), (held) => held.delete(key));
11361
11418
  }
11362
- /**
11363
- * The stores' views, read once and again after a store is written here (the only writer: pages in other tabs share
11364
- * this engine). An app's every storage call and chat frame checks them, and an index may be 4 MiB.
11365
- */
11419
+ /** The stores' views, made once and again after a store is written here: an app's every storage call and chat frame checks them. */
11366
11420
  heldViews() {
11367
11421
  return this.views ??= this.stores().then((s) => this.storeViews(s), (error) => {
11368
11422
  this.views = null;
11369
11423
  throw error;
11370
11424
  });
11371
11425
  }
11372
- /** The stores the person has (a removed default is not one). */
11426
+ /** The stores the person has (a removed default is not one), in the order of their keys, as IndexedDB lists them. */
11373
11427
  async stores() {
11374
- return (await wrap((await store(STORES$1.appStores, "readonly")).getAll())).filter((s) => !s.removed);
11428
+ return [...(await this.records()).values()].filter((s) => !s.removed).sort((a, b) => a.key < b.key ? -1 : 1);
11375
11429
  }
11376
11430
  storeViews(stores) {
11377
11431
  return stores.filter((s) => s.index).map((s) => ({
@@ -11513,12 +11567,43 @@ var Apps = class {
11513
11567
  if (read.has(url)) continue;
11514
11568
  read.add(url);
11515
11569
  try {
11516
- const read = readAppRevocations(await this.host.fetch(url, { maxBytes: APP_FETCH_LIMITS.revocationsBytes }));
11517
- if (read.ok) (out ??= []).push(...read.revocations.filter((r) => r.statement.app === ref));
11570
+ const file = await this.readRevocationsFile(await this.host.fetch(url, { maxBytes: APP_FETCH_LIMITS.revocationsBytes }));
11571
+ if (file) (out ??= []).push(...file.filter((r) => r.statement.app === ref));
11518
11572
  } catch {}
11519
11573
  }
11520
11574
  return out;
11521
11575
  }
11576
+ /**
11577
+ * `readAppRevocations` (one entry that does not verify refuses the file, null here), with a pause every few ms: the
11578
+ * engine shares the page's thread, and each entry is a signature check. The same bytes are not checked again.
11579
+ */
11580
+ async readRevocationsFile(bytes) {
11581
+ const id = toBase64Url(sha256(bytes));
11582
+ const known = this.revocationFiles.get(id);
11583
+ if (known !== void 0) return known;
11584
+ const read = readCanonicalJson(bytes);
11585
+ let out = null;
11586
+ if (read.ok && Array.isArray(read.value) && read.value.length <= APP_STATEMENT_LIMITS.revocations) {
11587
+ out = [];
11588
+ let since = performance.now();
11589
+ for (const entry of read.value) {
11590
+ if (performance.now() - since >= REVOCATIONS_SLICE_MS) {
11591
+ await new Promise((resolve) => setTimeout(resolve, 0));
11592
+ since = performance.now();
11593
+ }
11594
+ const one = readAppRevocation(entry);
11595
+ if (!one.ok) {
11596
+ out = null;
11597
+ break;
11598
+ }
11599
+ out.push(one.revocation);
11600
+ }
11601
+ }
11602
+ this.revocationFiles.delete(id);
11603
+ this.revocationFiles.set(id, out);
11604
+ for (const old of this.revocationFiles.keys()) if (this.revocationFiles.size > REVOCATION_FILES_KEPT) this.revocationFiles.delete(old);
11605
+ return out;
11606
+ }
11522
11607
  summary(s) {
11523
11608
  const index = s.index;
11524
11609
  return {
@@ -11675,8 +11760,10 @@ var Apps = class {
11675
11760
  problem: error instanceof AppFetchError ? error.code : "network"
11676
11761
  };
11677
11762
  }
11678
- if (!await this.storeRecord(s.key) || (await this.storeRecord(s.key))?.removed) continue;
11679
- await this.putStore(next);
11763
+ const now = await this.storeRecord(s.key);
11764
+ if (!now || now.removed) continue;
11765
+ if (now === s && next.index === s.index && next.digest === s.digest && next.problem === s.problem && next.equivocation === s.equivocation) (await this.records()).set(s.key, next);
11766
+ else await this.putStore(next);
11680
11767
  out.push(this.summary(next));
11681
11768
  }
11682
11769
  return out;
@@ -12214,7 +12301,11 @@ var Apps = class {
12214
12301
  const tx = (await openDb()).transaction([STORES$1.apps, STORES$1.appStorage], "readwrite");
12215
12302
  tx.objectStore(STORES$1.apps).delete(app.ref);
12216
12303
  tx.objectStore(STORES$1.appStorage).delete(rowsOf(app.ref));
12217
- await done(tx);
12304
+ try {
12305
+ await done(tx);
12306
+ } finally {
12307
+ this.held.delete(app.ref);
12308
+ }
12218
12309
  for (const key of [...this.usage.keys()]) if (key.startsWith(`${app.ref}\0`)) this.usage.delete(key);
12219
12310
  await this.removeBundle(app.bytes, app.digest);
12220
12311
  if (app.pending) await this.removeBundle(app.pending.bytes, app.pending.digest);
@@ -14629,6 +14720,8 @@ var EnrollInviter = class extends Enrollment {
14629
14720
  stopDoneWait = null;
14630
14721
  /** A joiner was seen on the link: from then it has as long as a joiner waits for the proof to open a session. */
14631
14722
  seen = false;
14723
+ /** The person answered the digits: one answer is taken, before the record is read or written for it. */
14724
+ answered = false;
14632
14725
  constructor(options) {
14633
14726
  super(options, {
14634
14727
  role: "inviter",
@@ -14743,7 +14836,8 @@ var EnrollInviter = class extends Enrollment {
14743
14836
  * and nothing was written.
14744
14837
  */
14745
14838
  async confirm(match) {
14746
- if (this.over || this.view.step !== "confirm" || !this.joiner) throw new Error("There are no digits to confirm");
14839
+ if (this.over || this.answered || this.view.step !== "confirm" || !this.joiner) throw new Error("There are no digits to confirm");
14840
+ this.answered = true;
14747
14841
  if (!match) {
14748
14842
  await this.fail("digits", "digits", this.joiner.name);
14749
14843
  return this.view;
@@ -20422,6 +20516,11 @@ var EditQueue = class {
20422
20516
  sent = /* @__PURE__ */ new Map();
20423
20517
  /** When a card that goes again last went (`CARD_RESTORE_GAP_MS`). */
20424
20518
  restoredAt = -Infinity;
20519
+ /** The rows known to have an edit on its way, each with when it was last told (`edited`), by `tick`. */
20520
+ known = /* @__PURE__ */ new Map();
20521
+ tick = 0;
20522
+ /** The chat's history was read for the rows with an edit on its way: `known` has them all, until the next session. */
20523
+ learnt = false;
20425
20524
  timer;
20426
20525
  flushing = null;
20427
20526
  again = false;
@@ -20464,17 +20563,48 @@ var EditQueue = class {
20464
20563
  */
20465
20564
  reopened() {
20466
20565
  this.sent.clear();
20566
+ this.learnt = false;
20567
+ }
20568
+ /** This row has an edit to send (it was just written so): the next look reads it. */
20569
+ edited(id) {
20570
+ this.known.set(id, ++this.tick);
20571
+ }
20572
+ /**
20573
+ * My rows with an edit on its way, by time. `all`: the chat's whole history, when it was read for them (no `row` to
20574
+ * read one by, or nothing learnt yet in this session). Otherwise the known rows are read alone, and one that waits
20575
+ * no more is forgotten, unless it was told again while it was read.
20576
+ */
20577
+ async pending() {
20578
+ const waits = (m) => m.sender === "me" && !!m.edit?.pending;
20579
+ const row = this.deps.row;
20580
+ if (!row || !this.learnt) {
20581
+ const all = await this.deps.read(), rows = all.filter(waits);
20582
+ for (const m of rows) if (!this.known.has(m.id)) this.known.set(m.id, this.tick);
20583
+ this.learnt = true;
20584
+ return {
20585
+ rows,
20586
+ all
20587
+ };
20588
+ }
20589
+ const asOf = this.tick, ids = [...this.known.keys()];
20590
+ const read = await Promise.all(ids.map((id) => row(id)));
20591
+ const rows = [];
20592
+ read.forEach((m, i) => {
20593
+ if (m && waits(m)) rows.push(m);
20594
+ else if ((this.known.get(ids[i]) ?? 0) <= asOf) this.known.delete(ids[i]);
20595
+ });
20596
+ return { rows: rows.sort((a, b) => a.timestamp - b.timestamp) };
20467
20597
  }
20468
20598
  /** The contact confirmed edit `seq` of my message with this wire id. */
20469
20599
  async received(wireId, seq) {
20470
- const row = (await this.deps.read()).find((m) => m.sender === "me" && m.wireId === wireId);
20600
+ const row = (await this.pending()).rows.find((m) => m.wireId === wireId);
20471
20601
  if (!row?.edit?.pending || row.edit.seq > seq) return;
20472
20602
  this.sent.delete(row.id);
20473
20603
  await this.deps.settle(row.id, row.edit.seq);
20474
20604
  }
20475
20605
  /** The contact confirmed, on the DHT floor, the edit that went there under this id (`dhtEditId`). */
20476
20606
  async receivedOnDht(id) {
20477
- const row = (await this.deps.read()).find((m) => m.sender === "me" && m.edit?.pending && m.wireId && dhtEditId(m.wireId, m.edit.seq) === id);
20607
+ const row = (await this.pending()).rows.find((m) => m.wireId && dhtEditId(m.wireId, m.edit.seq) === id);
20478
20608
  if (!row) return;
20479
20609
  this.sent.delete(row.id);
20480
20610
  await this.deps.settle(row.id, row.edit.seq);
@@ -20488,13 +20618,13 @@ var EditQueue = class {
20488
20618
  if (this.stopped) return;
20489
20619
  const now = this.now();
20490
20620
  const said = (m) => this.sent.get(m.id)?.seq === m.edit.seq ? 1 : 0;
20491
- const rows = await this.deps.read();
20621
+ const { rows, all } = await this.pending();
20622
+ const due = rows.filter((m) => m.wireId).sort((a, b) => said(a) - said(b) || a.edit.at - b.edit.at);
20492
20623
  const newest = /* @__PURE__ */ new Map();
20493
- for (const m of rows) {
20624
+ if (due.some((m) => m.edit.restore)) for (const m of all ?? await this.deps.read()) {
20494
20625
  const key = m.sender === "me" && cardKey(m);
20495
20626
  if (key && m.timestamp > (newest.get(key) ?? -Infinity)) newest.set(key, m.timestamp);
20496
20627
  }
20497
- const due = rows.filter((m) => m.sender === "me" && m.edit?.pending && m.wireId).sort((a, b) => said(a) - said(b) || a.edit.at - b.edit.at);
20498
20628
  const gap = this.deps.restoreGapMs ?? CARD_RESTORE_GAP_MS;
20499
20629
  let next = Infinity;
20500
20630
  for (const message of due) {
@@ -21297,9 +21427,8 @@ var db = {
21297
21427
  /** The group and its history. */
21298
21428
  async deleteGroup(groupId) {
21299
21429
  await wrap((await store(STORES$1.groups, "readwrite")).delete(groupId));
21300
- const messages = await store(STORES$1.messages, "readwrite");
21301
- const keys = await wrap(messages.index("byLink").getAllKeys(`group:${groupId}`));
21302
- await Promise.all(keys.map((key) => wrap(messages.delete(key))));
21430
+ const link = `group:${groupId}`;
21431
+ await wrap((await store(STORES$1.messages, "readwrite")).delete(IDBKeyRange.bound([link], [link, []])));
21303
21432
  },
21304
21433
  async getServices() {
21305
21434
  return wrap((await store(STORES$1.services, "readonly")).getAll());
@@ -21374,6 +21503,11 @@ function noteAfterChange(current, message) {
21374
21503
  snippet
21375
21504
  };
21376
21505
  }
21506
+ /** The note as its message's row has it now, when that reaction still stands there (not replaced, not taken back). */
21507
+ function noteIfStanding(current, message) {
21508
+ const r = message.reactions?.[current.by];
21509
+ return r?.e && r.e === current.emoji && r.at === current.at ? note(message, current.by, r.e, r.at) : void 0;
21510
+ }
21377
21511
  /**
21378
21512
  * My reactions in a private group to say again to an edge that opened, in case it missed them: the newest `limit`,
21379
21513
  * newest first (what the member is likeliest to have missed lands before its pace runs out), of this membership only.
@@ -21392,11 +21526,10 @@ function groupReactionsToResend(messages, limit) {
21392
21526
  }] : [];
21393
21527
  }).sort((a, b) => b.n - a.n).slice(0, limit);
21394
21528
  }
21395
- /** This side's highest number in a chat: the next one goes past it (and past what still waits to be confirmed). */
21396
- function lastMine(messages, pending = []) {
21529
+ /** This side's highest number in a chat's history: the next one goes past it. */
21530
+ function lastMine(messages) {
21397
21531
  let last = 0;
21398
21532
  for (const m of messages) last = Math.max(last, m.reactions?.me?.n ?? 0);
21399
- for (const p of pending) last = Math.max(last, p.n);
21400
21533
  return last;
21401
21534
  }
21402
21535
  var Reactions = class {
@@ -21407,6 +21540,11 @@ var Reactions = class {
21407
21540
  queue = Promise.resolve();
21408
21541
  /** A group member's reactions taken per window (chat and member key), as a paired chat's session limits them. */
21409
21542
  memberPace = /* @__PURE__ */ new Map();
21543
+ /**
21544
+ * This side's highest number per chat: read from its history the first time I react in it, then each number given
21545
+ * out. It only has to grow, so a row deleted since leaves it too high and nothing is wrong.
21546
+ */
21547
+ last = /* @__PURE__ */ new Map();
21410
21548
  constructor(host) {
21411
21549
  this.host = host;
21412
21550
  }
@@ -21428,17 +21566,19 @@ var Reactions = class {
21428
21566
  if (typeof messageId !== "string" || !messageId) return { error: "No message to react to" };
21429
21567
  const e = emoji === "" ? "" : reactionEmoji(emoji);
21430
21568
  if (e === null) return { error: "A reaction is one emoji" };
21431
- const messages = await this.host.messages(chat), group = isGroup$1(chat);
21432
- const target = messages.find((m) => m.id === messageId) ?? reactionTarget(messages, messageId, group);
21433
- const id = target && replyRef(target, group);
21569
+ const target = await this.named(chat, messageId);
21570
+ const id = target && replyRef(target, isGroup$1(chat));
21434
21571
  if (!target || !id) return { error: "That message is not in this chat, or takes no reaction" };
21435
21572
  const current = target.reactions?.me;
21436
21573
  if (!e && !current?.e) return { error: "No reaction of yours to take back" };
21574
+ let last = this.last.get(chat) ?? lastMine(await this.host.messages(chat));
21575
+ for (const p of pending) last = Math.max(last, p.n);
21437
21576
  const reaction = {
21438
21577
  id,
21439
21578
  e,
21440
- n: nextReactionNumber(lastMine(messages, pending), this.now())
21579
+ n: nextReactionNumber(last, this.now())
21441
21580
  };
21581
+ this.last.set(chat, reaction.n);
21442
21582
  return {
21443
21583
  reaction,
21444
21584
  message: await this.write(chat, target, "me", reaction) ?? target
@@ -21477,6 +21617,12 @@ var Reactions = class {
21477
21617
  for (const { by, reaction } of mine) await this.write(message.linkId, message, by, reaction);
21478
21618
  });
21479
21619
  }
21620
+ /** The row I react to: by its id here, as the pages name it, read alone; else by the id both sides know it by. */
21621
+ async named(chat, id) {
21622
+ if (this.host.message) return await this.host.message(chat, id) ?? this.find(chat, id);
21623
+ const messages = await this.host.messages(chat);
21624
+ return messages.find((m) => m.id === id) ?? reactionTarget(messages, id, isGroup$1(chat));
21625
+ }
21480
21626
  /**
21481
21627
  * The row a received reaction names. Most often the row of that id (a group's), or of `peer_` or `me_` and that id (a
21482
21628
  * chat's): those are read alone. Any other (a file's, a payment's, one not here yet) is looked for in the whole chat;
@@ -21493,9 +21639,10 @@ var Reactions = class {
21493
21639
  }
21494
21640
  return reactionTarget(await this.host.messages(chat), id, group);
21495
21641
  }
21496
- /** A chat is gone: nothing waits for it any more. */
21642
+ /** A chat is gone: nothing waits for it any more, and my highest number in it is read again if it comes back. */
21497
21643
  forget(chat) {
21498
21644
  this.waiting.delete(chat);
21645
+ this.last.delete(chat);
21499
21646
  }
21500
21647
  wait(chat, by, reaction) {
21501
21648
  const now = this.now();
@@ -25569,6 +25716,8 @@ var Groups = class {
25569
25716
  return;
25570
25717
  }
25571
25718
  if (existing?.invitation?.seedB64 && (existing.invitation.linkId !== linkId || existing.invitation.admin !== frame.admin)) return;
25719
+ if (existing?.left && (existing.left.admin !== frame.admin || existing.contacts?.[existing.left.admin] !== linkId)) return;
25720
+ if (existing?.invitation && !existing.state && existing.invitation.linkId !== linkId && !existing.invitation.genesis) return;
25572
25721
  if ([...this.stored.values()].filter((x) => x.invitation).length >= 32) return;
25573
25722
  if (!existing?.invitation && [...this.stored.values()].filter((x) => x.invitation?.linkId === linkId).length >= MAX_INVITATIONS_PER_CHAT) {
25574
25723
  try {
@@ -25584,6 +25733,7 @@ var Groups = class {
25584
25733
  ...existing?.state?.nicks
25585
25734
  };
25586
25735
  const previous = existing?.state ? existing : existing?.previous;
25736
+ const genesis = existing?.state ? commitHash(existing.state.chain[0]) : existing?.invitation?.genesis;
25587
25737
  const invitation = {
25588
25738
  id: g,
25589
25739
  createdAt: Date.now(),
@@ -25592,7 +25742,8 @@ var Groups = class {
25592
25742
  admin: frame.admin,
25593
25743
  linkId,
25594
25744
  ...invitationClaims(frame),
25595
- pieces: []
25745
+ pieces: [],
25746
+ ...genesis ? { genesis } : {}
25596
25747
  },
25597
25748
  ...Object.keys(known).length ? { formerNames: known } : {},
25598
25749
  ...previous ? { previous } : {}
@@ -25676,6 +25827,20 @@ var Groups = class {
25676
25827
  group.invitation.pieces = [];
25677
25828
  return;
25678
25829
  }
25830
+ if (group.invitation.genesis && commitHash(joined.state.chain[0]) !== group.invitation.genesis) {
25831
+ try {
25832
+ this.host.sendOnLink(linkId, {
25833
+ t: "group-decline",
25834
+ g
25835
+ });
25836
+ } catch {}
25837
+ delete group.invitation.seedB64;
25838
+ group.invitation.pieces = [];
25839
+ this.acceptSaid.delete(g);
25840
+ await this.store.putGroup(group);
25841
+ this.host.emit();
25842
+ return;
25843
+ }
25679
25844
  const viaLink = !!group.invitation.entry;
25680
25845
  this.meet(g, group.invitation.admin);
25681
25846
  const member = {
@@ -26708,6 +26873,14 @@ var GroupEdits = class {
26708
26873
  receivePace = /* @__PURE__ */ new Map();
26709
26874
  timers = /* @__PURE__ */ new Map();
26710
26875
  flushing = /* @__PURE__ */ new Map();
26876
+ /** Per group: my rows known to have an edit to say, each with when it was last told (`edit`), by `tick`. */
26877
+ known = /* @__PURE__ */ new Map();
26878
+ tick = 0;
26879
+ /**
26880
+ * The groups whose history was read for the rows with an edit to say: `known` has them all, and from then on those
26881
+ * rows alone are read (`host.message`). An edit costs what it does in a short group, however long this one is.
26882
+ */
26883
+ learnt = /* @__PURE__ */ new Set();
26711
26884
  stopped = false;
26712
26885
  constructor(host) {
26713
26886
  this.host = host;
@@ -26758,6 +26931,7 @@ var GroupEdits = class {
26758
26931
  mentions: mentions.length ? mentions : void 0
26759
26932
  };
26760
26933
  });
26934
+ if (this.host.message) this.told(groupId).set(messageId, ++this.tick);
26761
26935
  await this.host.changed(chat, messageId);
26762
26936
  await this.flush(groupId).catch(() => {});
26763
26937
  return {
@@ -26778,7 +26952,7 @@ var GroupEdits = class {
26778
26952
  this.timers.delete(groupId);
26779
26953
  if (this.stopped || !this.host.membership(groupId)) return;
26780
26954
  const chat = chatOf(groupId), now = this.now();
26781
- const due = (await this.host.messages(chat)).filter((m) => m.sender === "me" && m.edit?.pending).sort((a, b) => a.edit.at - b.edit.at);
26955
+ const due = (await this.pending(groupId)).sort((a, b) => a.edit.at - b.edit.at);
26782
26956
  let pace = this.sendPace.get(groupId);
26783
26957
  if (!pace) this.sendPace.set(groupId, pace = new RateWindow(10, EDIT_RATE_WINDOW_MS, this.now));
26784
26958
  let retry = Infinity;
@@ -26809,6 +26983,33 @@ var GroupEdits = class {
26809
26983
  this.flush(groupId).catch(() => {});
26810
26984
  }, Math.max(50, retry)));
26811
26985
  }
26986
+ /**
26987
+ * My rows of a group with an edit to say. The group's whole history is read for them once (always, with no
26988
+ * `host.message` to read one by); after that the known rows alone, and one that waits no more is forgotten, unless
26989
+ * it was told again while it was read.
26990
+ */
26991
+ async pending(groupId) {
26992
+ const waits = (m) => m.sender === "me" && !!m.edit?.pending;
26993
+ const chat = chatOf(groupId), read = this.host.message;
26994
+ if (!read || !this.learnt.has(groupId)) {
26995
+ const rows = (await this.host.messages(chat)).filter(waits);
26996
+ if (!read) return rows;
26997
+ for (const m of rows) if (!this.told(groupId).has(m.id)) this.told(groupId).set(m.id, this.tick);
26998
+ this.learnt.add(groupId);
26999
+ return rows;
27000
+ }
27001
+ const known = this.told(groupId), asOf = this.tick, ids = [...known.keys()];
27002
+ return (await Promise.all(ids.map((id) => read(chat, id)))).filter((m, i) => {
27003
+ if (m && waits(m)) return true;
27004
+ if ((known.get(ids[i]) ?? 0) <= asOf) known.delete(ids[i]);
27005
+ return false;
27006
+ });
27007
+ }
27008
+ told(groupId) {
27009
+ let known = this.known.get(groupId);
27010
+ if (!known) this.known.set(groupId, known = /* @__PURE__ */ new Map());
27011
+ return known;
27012
+ }
26812
27013
  async find(chat, id) {
26813
27014
  return this.host.message ? this.host.message(chat, id) : (await this.host.messages(chat)).find((m) => m.id === id);
26814
27015
  }
@@ -26894,6 +27095,8 @@ var GroupEdits = class {
26894
27095
  forget(groupId) {
26895
27096
  clearTimeout(this.timers.get(groupId));
26896
27097
  this.timers.delete(groupId);
27098
+ this.known.delete(groupId);
27099
+ this.learnt.delete(groupId);
26897
27100
  this.buffer.forget(chatOf(groupId));
26898
27101
  }
26899
27102
  stop() {
@@ -30682,6 +30885,9 @@ var CashuWallet = class CashuWallet {
30682
30885
  * taken back.
30683
30886
  */
30684
30887
  var UNIT = "sat";
30888
+ /** A contact's requests in one chat: the new ones taken per minute, and how many may wait to be paid. */
30889
+ var REQUESTS_PER_MINUTE = 20;
30890
+ var OPEN_REQUESTS = 50;
30685
30891
  var satsWord = (n) => `${n} sat${n === 1 ? "" : "s"}`;
30686
30892
  /**
30687
30893
  * A refused send's review, once its ecash came back: why, and exactly what came back. Taking ecash back is a swap at
@@ -30748,10 +30954,32 @@ var allows = (link, method) => method === "arkade" ? link.supportsArkPayments :
30748
30954
  var fedimintSats = (msats) => Math.floor(msats / 1e3);
30749
30955
  /** The distinct wallets of a per-network set (one object may stand for both). */
30750
30956
  var distinct = (each) => [...new Set(WALLET_NETWORKS.map((n) => each[n]))];
30957
+ /**
30958
+ * Every payment this device keeps, with the copy of them the pages get. The copy is made again only after a payment
30959
+ * changed: a state about anything else (typing, a file's progress) carries the one already made.
30960
+ */
30961
+ var PaymentBook = class extends Map {
30962
+ shown = null;
30963
+ set(id, payment) {
30964
+ this.shown = null;
30965
+ return super.set(id, payment);
30966
+ }
30967
+ delete(id) {
30968
+ this.shown = null;
30969
+ return super.delete(id);
30970
+ }
30971
+ clear() {
30972
+ this.shown = null;
30973
+ super.clear();
30974
+ }
30975
+ views() {
30976
+ return this.shown ??= Object.fromEntries([...this].map(([id, { token: _token, ...view }]) => [id, view]));
30977
+ }
30978
+ };
30751
30979
  var PaymentDesk = class {
30752
30980
  wallet;
30753
30981
  host;
30754
- payments = /* @__PURE__ */ new Map();
30982
+ payments = new PaymentBook();
30755
30983
  paying = /* @__PURE__ */ new Map();
30756
30984
  receiving = /* @__PURE__ */ new Map();
30757
30985
  reclaims = /* @__PURE__ */ new Map();
@@ -30813,8 +31041,9 @@ var PaymentDesk = class {
30813
31041
  records() {
30814
31042
  return [...this.payments.values()];
30815
31043
  }
31044
+ /** What the pages see of every payment. The same record until a payment changes: read it, never write to it. */
30816
31045
  views() {
30817
- return Object.fromEntries([...this.payments].map(([id, { token: _token, ...view }]) => [id, view]));
31046
+ return this.payments.views();
30818
31047
  }
30819
31048
  async forgetLink(linkId) {
30820
31049
  const paymentStore = await store(STORES$1.payments, "readwrite");
@@ -30823,6 +31052,7 @@ var PaymentDesk = class {
30823
31052
  this.payments.delete(payment.id);
30824
31053
  wrap(paymentStore.delete(payment.id));
30825
31054
  }
31055
+ this.requestsFrom.delete(linkId);
30826
31056
  }
30827
31057
  /** `confirmedReal`: the person confirmed a Mainnet send as real money; without it one is refused. */
30828
31058
  async send(params) {
@@ -31477,9 +31707,24 @@ var PaymentDesk = class {
31477
31707
  endpoints: [...payment.invoice ? [[ENDPOINT.bolt11, payment.invoice]] : [], ...payment.mints?.length ? [[ENDPOINT.cashu, cashuRequestPayload(payment.mints)]] : []]
31478
31708
  };
31479
31709
  }
31710
+ /** New requests taken from each chat's contact lately. */
31711
+ requestsFrom = /* @__PURE__ */ new Map();
31712
+ /**
31713
+ * Whether this chat has room for one more request of its contact: twenty new ones a minute, and fifty waiting to be
31714
+ * paid at most. One left out is not lost: the contact's app sends its open requests again on every session.
31715
+ */
31716
+ roomForRequest(linkId) {
31717
+ let recent = this.requestsFrom.get(linkId);
31718
+ if (!recent) this.requestsFrom.set(linkId, recent = new RateWindow(REQUESTS_PER_MINUTE, 6e4));
31719
+ if (!recent.take()) return false;
31720
+ let open = 0;
31721
+ for (const p of this.payments.values()) if (p.linkId === linkId && p.kind === "request" && p.direction === "in" && p.state === "pending" && ++open >= OPEN_REQUESTS) return false;
31722
+ return true;
31723
+ }
31480
31724
  /** `held`: picked up from the contact's storage while it was away (WISP 404): only what this device allows counts, and only Cashu or Lightning. */
31481
31725
  async onPaymentRequest(linkId, request, held = false) {
31482
31726
  if (this.find(linkId, request.id)?.linkId === linkId) return;
31727
+ if (!this.roomForRequest(linkId)) return;
31483
31728
  if (held && (findEndpoint(request.endpoints, ENDPOINT.usdt) || findEndpoint(request.endpoints, ENDPOINT.arkade) || findEndpoint(request.endpoints, ENDPOINT.bark) || findEndpoint(request.endpoints, ENDPOINT.bitcoin) || findEndpoint(request.endpoints, ENDPOINT.fedimint) || findEndpoint(request.endpoints, ENDPOINT.spark))) return;
31484
31729
  if (findEndpoint(request.endpoints, ENDPOINT.usdt)) return this.receiveUsdtRequest(linkId, request);
31485
31730
  const amount = parseSats(request.amount.value, request.amount.asset);
@@ -32043,6 +32288,22 @@ var PaymentDesk = class {
32043
32288
  endpoint: [ENDPOINT.usdt, JSON.stringify({ txid: review.txid })]
32044
32289
  });
32045
32290
  }
32291
+ /**
32292
+ * One payment pays one request: a receipt for a request that already has one, under another payment id,
32293
+ * is not another payment. Nothing is stored or shown for it; a paid request is answered again.
32294
+ */
32295
+ repeatedReceipt(link, request, id) {
32296
+ let repeated = false;
32297
+ for (const p of this.payments.values()) if (p.id !== id && p.kind === "payment" && p.direction === "in" && p.requestId === request.id && p.linkId === request.linkId && p.state !== "failed") {
32298
+ repeated = true;
32299
+ break;
32300
+ }
32301
+ if (repeated && request.state === "settled") link?.sendPaymentResult({
32302
+ id: request.id,
32303
+ ok: true
32304
+ });
32305
+ return repeated;
32306
+ }
32046
32307
  async receiveUsdt(linkId, payment) {
32047
32308
  const request = payment.requestId ? this.payments.get(payment.requestId) : void 0;
32048
32309
  if (!this.host.getLink(linkId)?.supportsUsdtPayments || request?.target?.method !== "usdt" || request.linkId !== linkId || request.direction !== "out" || request.kind !== "request" || payment.amount.asset !== request.unit || payment.amount.value !== String(request.amount)) return;
@@ -32055,6 +32316,7 @@ var PaymentDesk = class {
32055
32316
  return;
32056
32317
  }
32057
32318
  if (typeof txid !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(txid) || existing?.state === "settled") return;
32319
+ if (this.repeatedReceipt(this.host.getLink(linkId), request, payment.id)) return;
32058
32320
  txid = txid.toLowerCase();
32059
32321
  if ([...this.payments.values()].some((p) => p.txid?.toLowerCase() === txid && (p.linkId !== linkId || (p.kind === "request" ? p.id !== request.id : p.requestId !== request.id)))) return;
32060
32322
  await this.save({
@@ -32222,7 +32484,7 @@ var PaymentDesk = class {
32222
32484
  }
32223
32485
  if ([...this.payments.values()].some((p) => p.txid === txid && (p.linkId !== linkId || (p.kind === "request" ? p.id !== request.id : p.requestId !== request.id)))) return;
32224
32486
  if (typeof txid !== "string" || !/^[a-f0-9]{64}$/.test(txid) || existing?.state === "settled") return;
32225
- if (!existing && [...this.payments.values()].some((p) => p.kind === "payment" && p.direction === "in" && p.requestId === request.id && p.state === "pending")) return;
32487
+ if (this.repeatedReceipt(link, request, payment.id)) return;
32226
32488
  await this.save({
32227
32489
  id: payment.id,
32228
32490
  linkId,
@@ -32340,6 +32602,7 @@ var PaymentDesk = class {
32340
32602
  const existing = this.payments.get(payment.id);
32341
32603
  if (existing && (existing.linkId !== linkId || existing.direction !== "in" || existing.requestId !== request.id)) return;
32342
32604
  if (existing || parseSats(payment.amount.value, payment.amount.asset) !== request.amount) return;
32605
+ if (this.repeatedReceipt(link, request, payment.id)) return;
32343
32606
  await this.save({
32344
32607
  id: payment.id,
32345
32608
  linkId,
@@ -32476,6 +32739,7 @@ var PaymentDesk = class {
32476
32739
  return;
32477
32740
  }
32478
32741
  if (typeof hint !== "string" || !/^[0-9a-f]{64}$/.test(hint)) return;
32742
+ if (this.repeatedReceipt(link, request, payment.id)) return;
32479
32743
  const settled = request.state === "settled";
32480
32744
  await this.save({
32481
32745
  id: payment.id,
@@ -32824,6 +33088,7 @@ var PaymentDesk = class {
32824
33088
  const existing = this.payments.get(payment.id);
32825
33089
  if (existing && (existing.linkId !== linkId || existing.direction !== "in" || existing.requestId !== request.id)) return;
32826
33090
  if (existing || parseSats(payment.amount.value, payment.amount.asset) !== request.amount) return;
33091
+ if (this.repeatedReceipt(link, request, payment.id)) return;
32827
33092
  const settled = request.state === "settled";
32828
33093
  await this.save({
32829
33094
  id: payment.id,
@@ -32947,6 +33212,20 @@ var PaymentDesk = class {
32947
33212
  });
32948
33213
  }
32949
33214
  /**
33215
+ * A contact's receipt that still waits for its money, dropped by hand (its chat line was deleted here): the
33216
+ * request it answers can take another receipt. Money that does arrive still pays the request.
33217
+ */
33218
+ async dropReceipt(id) {
33219
+ const payment = this.payments.get(id), request = payment?.requestId ? this.payments.get(payment.requestId) : void 0;
33220
+ if (payment?.kind !== "payment" || payment.direction !== "in" || payment.state !== "pending" || !payment.target) return;
33221
+ if (request?.kind !== "request" || request.direction !== "out" || request.linkId !== payment.linkId || request.target?.method !== payment.target.method) return;
33222
+ await this.save({
33223
+ ...payment,
33224
+ state: "failed",
33225
+ error: "Removed here"
33226
+ });
33227
+ }
33228
+ /**
32950
33229
  * A request of ours closed for good: the wallet it was paid through was removed. Nothing sends it again (replay,
32951
33230
  * a group edge opening), and the contact is told now when the chat is open (a request to a group: every member
32952
33231
  * reachable), so their app stops offering to pay it. `reason`: shown here; `told`: what the contact reads. Returns
@@ -33847,7 +34126,7 @@ var GhostlyNode = class GhostlyNode {
33847
34126
  cue: "downloaded",
33848
34127
  key: fileId
33849
34128
  }, this.chatOf(linkId));
33850
- this.noteFileEnd(linkId, fileId, record.state === "done" ? void 0 : record.error ?? record.state);
34129
+ this.noteFileEnd(linkId, fileId, record.state === "done" ? void 0 : record.error ?? record.state, !seen);
33851
34130
  }
33852
34131
  });
33853
34132
  stateTimer = null;
@@ -34029,6 +34308,7 @@ var GhostlyNode = class GhostlyNode {
34029
34308
  descriptors: () => this.providers().lightning,
34030
34309
  host: () => this.providerHost(network),
34031
34310
  hasMints: () => this.networkMints(network).length > 0,
34311
+ fetch: (...request) => (this.options.lnurlFetch ?? globalThis.fetch)(...request),
34032
34312
  events: {
34033
34313
  changed: () => void this.refreshWallet(),
34034
34314
  received: (op) => void this.desk.onLightningPaid(op),
@@ -34459,6 +34739,8 @@ var GhostlyNode = class GhostlyNode {
34459
34739
  * again those not confirmed after a while.
34460
34740
  */
34461
34741
  reactionNotes = /* @__PURE__ */ new Map();
34742
+ /** Each chat's notes shown before the one shown now, oldest first, the latest `REACTION_NOTES_KEPT` of this run. */
34743
+ reactionNotesBefore = /* @__PURE__ */ new Map();
34462
34744
  reactionsSent = /* @__PURE__ */ new Map();
34463
34745
  reactionPace = /* @__PURE__ */ new Map();
34464
34746
  reactionTimers = /* @__PURE__ */ new Map();
@@ -34480,9 +34762,12 @@ var GhostlyNode = class GhostlyNode {
34480
34762
  message: (chat, id) => db.getMessage(chat, id),
34481
34763
  patch: (chat, id, change) => db.patchMessage(chat, id, change),
34482
34764
  changed: async (chat, id, note) => {
34483
- if (note) this.reactionNotes.set(chat, note);
34484
- else if (this.reactionNotes.get(chat)?.message === id) {
34485
- const before = latestReaction(await db.getMessages(chat));
34765
+ const shown = this.reactionNotes.get(chat);
34766
+ if (note) {
34767
+ if (shown && (shown.message !== note.message || shown.by !== note.by)) this.reactionNotesBefore.set(chat, [...this.reactionNotesBefore.get(chat) ?? [], shown].slice(-8));
34768
+ this.reactionNotes.set(chat, note);
34769
+ } else if (shown?.message === id) {
34770
+ const before = await this.reactionNoteNow(chat, shown);
34486
34771
  if (before) this.reactionNotes.set(chat, before);
34487
34772
  else this.reactionNotes.delete(chat);
34488
34773
  }
@@ -34491,6 +34776,19 @@ var GhostlyNode = class GhostlyNode {
34491
34776
  },
34492
34777
  notify: (chat, key) => this.feedback("reaction", `${chat}:${key}`, chat)
34493
34778
  });
34779
+ /**
34780
+ * The chat list's reaction once one to its message was taken back: the same one if it still stands, else the latest
34781
+ * shown before it that does, each read from its own row. Only past those is the whole history read for an older one.
34782
+ */
34783
+ async reactionNoteNow(chat, shown) {
34784
+ const before = this.reactionNotesBefore.get(chat) ?? [];
34785
+ for (let note = shown; note; note = before.pop()) {
34786
+ const row = note.message ? await db.getMessage(chat, note.message) : void 0;
34787
+ const standing = row && noteIfStanding(note, row);
34788
+ if (standing) return standing;
34789
+ }
34790
+ return latestReaction(await db.getMessages(chat));
34791
+ }
34494
34792
  /** Private groups (WISP 900): sessions, admission on contact chats, and the pairwise edges that carry them. */
34495
34793
  groups = new Groups({
34496
34794
  sendOnLink: (linkId, frame) => {
@@ -35594,17 +35892,27 @@ var GhostlyNode = class GhostlyNode {
35594
35892
  ...this.settings.wakeMutedGroups?.includes(group.id) && { wakeMuted: true }
35595
35893
  };
35596
35894
  }
35895
+ /** Per community, the members a payment here names, as read from one copy of the payments (`PaymentDesk.views`). */
35896
+ communityPaid;
35597
35897
  /** Community members payments go to (or came from), as links the payment bubbles and the composer look up by key. */
35598
35898
  communityPayViews(groups) {
35599
- const withPayments = /* @__PURE__ */ new Map();
35600
- for (const p of Object.values(this.desk.views())) {
35601
- const at = parsePayLink(p.linkId);
35602
- if (at?.member) {
35603
- const set = withPayments.get(at.groupId) ?? /* @__PURE__ */ new Set();
35604
- set.add(at.member);
35605
- withPayments.set(at.groupId, set);
35899
+ const payments = this.desk.views();
35900
+ if (this.communityPaid?.of !== payments) {
35901
+ const members = /* @__PURE__ */ new Map();
35902
+ for (const p of Object.values(payments)) {
35903
+ const at = parsePayLink(p.linkId);
35904
+ if (at?.member) {
35905
+ const set = members.get(at.groupId) ?? /* @__PURE__ */ new Set();
35906
+ set.add(at.member);
35907
+ members.set(at.groupId, set);
35908
+ }
35606
35909
  }
35910
+ this.communityPaid = {
35911
+ of: payments,
35912
+ members
35913
+ };
35607
35914
  }
35915
+ const withPayments = this.communityPaid.members;
35608
35916
  return groups.filter((g) => g.profile === "community" && g.status === "active" && g.myKey).flatMap((g) => this.communityPay.views(g.id, g.myKey, g.members.map((m) => m.key), withPayments.get(g.id) ?? /* @__PURE__ */ new Set()));
35609
35917
  }
35610
35918
  /** The payment composer opened on a member of a community: ask what they take, if they did not say lately. */
@@ -36187,6 +36495,7 @@ var GhostlyNode = class GhostlyNode {
36187
36495
  this.newestAt.delete(linkId);
36188
36496
  this.placedAt.delete(linkId);
36189
36497
  this.reactionNotes.delete(linkId);
36498
+ this.reactionNotesBefore.delete(linkId);
36190
36499
  this.events.onMessages(linkId, []);
36191
36500
  }
36192
36501
  async setGroupWakeMuted(groupId, muted) {
@@ -36734,7 +37043,9 @@ var GhostlyNode = class GhostlyNode {
36734
37043
  card: edited.card
36735
37044
  }));
36736
37045
  await this.messagesChanged(linkId, [message.id]);
36737
- this.editsFor(linkId).flush().catch(() => {});
37046
+ const edits = this.editsFor(linkId);
37047
+ edits.edited(message.id);
37048
+ edits.flush().catch(() => {});
36738
37049
  return {
36739
37050
  error: null,
36740
37051
  messageId: message.id
@@ -36855,7 +37166,10 @@ var GhostlyNode = class GhostlyNode {
36855
37166
  },
36856
37167
  ...done
36857
37168
  };
36858
- }))?.edit?.pending) changed.push(message.id);
37169
+ }))?.edit?.pending) {
37170
+ changed.push(message.id);
37171
+ this.editsFor(linkId).edited(message.id);
37172
+ }
36859
37173
  }
36860
37174
  if (changed.length) await this.messagesChanged(linkId, changed);
36861
37175
  }
@@ -36865,6 +37179,7 @@ var GhostlyNode = class GhostlyNode {
36865
37179
  if (!queue) {
36866
37180
  queue = new EditQueue({
36867
37181
  read: () => db.getMessages(linkId),
37182
+ row: (id) => db.getMessage(linkId, id),
36868
37183
  ready: () => {
36869
37184
  const live = this.links.get(linkId), link = live?.link;
36870
37185
  return !!link && (link.supportsEdits || link.textDelivery === "dht" && this.takesDhtEdits(live));
@@ -37168,12 +37483,34 @@ var GhostlyNode = class GhostlyNode {
37168
37483
  }));
37169
37484
  return Promise.resolve();
37170
37485
  }
37171
- /** A file transfer ended: when, on the message that carries the file. */
37172
- async noteFileEnd(linkId, fileId, error) {
37173
- const message = (await db.getMessages(linkId)).find((m) => m.file?.id === fileId);
37486
+ /**
37487
+ * The message that carries a file, read by its id: a file of mine is `me_<its time>`, a received one is named by its
37488
+ * wire id (files/3, a held file) or by its sender's time (files/2). The chat's history is read only when none of
37489
+ * those rows carries it, so a file costs the same in a long chat as in a short one.
37490
+ */
37491
+ async fileMessage(linkId, fileId) {
37492
+ const stored = await fileStore.get(fileId).catch(() => void 0);
37493
+ const at = stored?.metadata?.timestamp, wireId = stored?.wireId;
37494
+ const ids = !stored ? [] : fileId.startsWith(`${linkId}-out-`) ? [`me_${at}`] : [
37495
+ `peer_${wireId}`,
37496
+ `peer_${at}`,
37497
+ `peer_${at}_${wireId}`
37498
+ ];
37499
+ for (const id of ids) {
37500
+ const message = await db.getMessage(linkId, id);
37501
+ if (message?.file?.id === fileId) return message;
37502
+ }
37503
+ return (await db.getMessages(linkId)).find((m) => m.file?.id === fileId);
37504
+ }
37505
+ /**
37506
+ * A file transfer ended: when, on the message that carries the file. `again`: it ended in an earlier run and is shown
37507
+ * again as the chat connects, so what its message says of its end stays (only a receipt it never got is given).
37508
+ */
37509
+ async noteFileEnd(linkId, fileId, error, again = false) {
37510
+ const message = await this.fileMessage(linkId, fileId);
37174
37511
  if (!message) return;
37175
37512
  const now = Date.now();
37176
- await this.noteDetails(linkId, message.id, (details) => error ? message.sender === "me" && details.sends?.length ? {
37513
+ if (!again) await this.noteDetails(linkId, message.id, (details) => error ? message.sender === "me" && details.sends?.length ? {
37177
37514
  ...details,
37178
37515
  sends: details.sends.map((s, i) => i === details.sends.length - 1 ? {
37179
37516
  ...s,
@@ -37223,6 +37560,7 @@ var GhostlyNode = class GhostlyNode {
37223
37560
  (async () => {
37224
37561
  const message = await db.getMessage(linkId, messageId);
37225
37562
  if (message?.paymentId && message.delivery === "waiting") await this.desk.withdraw(message.paymentId).catch(() => {});
37563
+ if (message?.paymentId && message.sender === "peer") await this.desk.dropReceipt(message.paymentId).catch(() => {});
37226
37564
  if (message?.sender === "me" && message.file && message.delivery === "waiting" && (message.fileOnFloor === "sent" || message.fileOnFloor === "seen")) {
37227
37565
  const wireId = message.file.id.slice(`${linkId}-out-`.length);
37228
37566
  live.link?.settleDht(dhtFileId(wireId)).catch(() => {});
@@ -37371,7 +37709,7 @@ var GhostlyNode = class GhostlyNode {
37371
37709
  });
37372
37710
  (async () => {
37373
37711
  if (!reply || !forwarded) {
37374
- const message = (await db.getMessages(live.stored.id)).find((m) => m.id === `me_${timestamp}`);
37712
+ const message = await db.getMessage(live.stored.id, `me_${timestamp}`);
37375
37713
  reply ??= message && GhostlyNode.wireReply(message);
37376
37714
  forwarded ??= message?.forwarded;
37377
37715
  }
@@ -38121,8 +38459,7 @@ var GhostlyNode = class GhostlyNode {
38121
38459
  error: "No button to press",
38122
38460
  refused: true
38123
38461
  };
38124
- const history = await db.getMessages(linkId);
38125
- const message = history.find((m) => m.id === messageId);
38462
+ const message = await db.getMessage(linkId, messageId);
38126
38463
  if (!message) return {
38127
38464
  error: "That message is not in this chat",
38128
38465
  refused: true
@@ -38131,6 +38468,8 @@ var GhostlyNode = class GhostlyNode {
38131
38468
  error: "Buttons on your own message do nothing",
38132
38469
  refused: true
38133
38470
  };
38471
+ const card = messageButtons(message);
38472
+ const history = card && !card.closed && card.buttons.some((b) => b.once) ? await db.getMessages(linkId) : [];
38134
38473
  const state = buttonsState(message, replyRef(message), history);
38135
38474
  const button = state?.card.buttons.find((b) => b.id === buttonId);
38136
38475
  if (!state || !button) return {
@@ -38179,8 +38518,7 @@ var GhostlyNode = class GhostlyNode {
38179
38518
  if (!targets.length) throw new Error("No chat to forward to");
38180
38519
  if (targets.length > 5) throw new Error(`Forward to at most 5 chats at once`);
38181
38520
  if (linkId.startsWith("group:") ? !this.membership(linkId.slice(6)) : this.links.get(linkId)?.stored.group !== void 0 || !this.links.has(linkId)) throw new Error("Chat not found");
38182
- const history = await db.getMessages(linkId);
38183
- const chosen = ids.map((id) => history.find((m) => m.id === id));
38521
+ const chosen = await Promise.all(ids.map((id) => db.getMessage(linkId, id)));
38184
38522
  if (chosen.some((m) => !m)) throw new Error("That message is not in this chat");
38185
38523
  const messages = chosen.sort((a, b) => a.timestamp - b.timestamp);
38186
38524
  for (const message of messages) {
@@ -38512,11 +38850,11 @@ var GhostlyNode = class GhostlyNode {
38512
38850
  const live = typeof linkId === "string" ? this.links.get(linkId) : void 0;
38513
38851
  if (!live || live.stored.group || !live.stored.profile) throw new Error("No such chat");
38514
38852
  if (!isAppRef(ref)) throw new Error("Not an app reference");
38515
- const { participationSeed, pairedPeerKey } = live.stored;
38516
- if (!participationSeed || !pairedPeerKey) throw new Error("This chat is not paired yet");
38853
+ const mine = participationKeyOf(live), { pairedPeerKey } = live.stored;
38854
+ if (!mine || !pairedPeerKey) throw new Error("This chat is not paired yet");
38517
38855
  return {
38518
38856
  live,
38519
- app: chatAppId(identityFromSeedB64(participationSeed).publicKey, publicKeyFromZ32(pairedPeerKey), ref)
38857
+ app: chatAppId(publicKeyFromZ32(mine), publicKeyFromZ32(pairedPeerKey), ref)
38520
38858
  };
38521
38859
  }
38522
38860
  /** The chat app id of an app in a paired chat: how the contact's frames name it. */
@@ -41431,7 +41769,7 @@ var GhostlyNode = class GhostlyNode {
41431
41769
  get nativeFactories() {
41432
41770
  const relay = this.relaysHyperdht ? this.hyperdhtRelay : "";
41433
41771
  return {
41434
- ...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-DmTrpmba.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
41772
+ ...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-C0XRWySP.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
41435
41773
  ...this.options.nativeTransports,
41436
41774
  ...this.ownIrohRelays ? { "iroh/1": (seedB64) => this.options.nativeTransports["iroh/1"](seedB64, this.settings.irohRelays?.length ? { relays: [...this.settings.irohRelays] } : {}) } : {},
41437
41775
  ...this.options.irohWeb ? { "iroh/1": (seedB64) => createIrohWebEndpoint(seedB64, { relays: this.irohRelays }) } : {}
@@ -42236,4 +42574,4 @@ function signalKind(signal) {
42236
42574
  //#endregion
42237
42575
  export { EngineServer, hyperdhtRelayProblem as n, server_exports as t };
42238
42576
 
42239
- //# sourceMappingURL=server-BGXmSYx_.js.map
42577
+ //# sourceMappingURL=server-D5FBZYW8.js.map