@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.
- package/README.md +11 -8
- package/SKILL.md +13 -5
- package/dist/assets/{arkade-3szZ1IpP.js → arkade-CtCpFaF7.js} +2 -2
- package/dist/assets/{arkade-3szZ1IpP.js.map → arkade-CtCpFaF7.js.map} +1 -1
- package/dist/assets/{hyperdhtRelay-DmTrpmba.js → hyperdhtRelay-C0XRWySP.js} +3 -3
- package/dist/assets/{hyperdhtRelay-DmTrpmba.js.map → hyperdhtRelay-C0XRWySP.js.map} +1 -1
- package/dist/assets/{mainline-BueFJx3f.js → mainline-DVYdrus6.js} +2 -2
- package/dist/assets/{mainline-BueFJx3f.js.map → mainline-DVYdrus6.js.map} +1 -1
- package/dist/assets/{server-BGXmSYx_.js → server-D5FBZYW8.js} +412 -74
- package/dist/assets/server-D5FBZYW8.js.map +1 -0
- package/dist/assets/{src-BMUcYQ7b.js → src-aKGjb_yz.js} +107 -22
- package/dist/assets/src-aKGjb_yz.js.map +1 -0
- package/dist/assets/{usdt-DklGBSeq.js → usdt-CUfucwZf.js} +2 -2
- package/dist/assets/{usdt-DklGBSeq.js.map → usdt-CUfucwZf.js.map} +1 -1
- package/dist/ghostly.mjs +478 -135
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/assets/server-BGXmSYx_.js.map +0 -1
- package/dist/assets/src-BMUcYQ7b.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import { $a as
|
|
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-
|
|
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-
|
|
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
|
-
|
|
11304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
11355
|
-
|
|
11356
|
-
this.views = null;
|
|
11413
|
+
putStore(record) {
|
|
11414
|
+
return this.writeStores((records) => records.put(record), (held) => held.set(record.key, record));
|
|
11357
11415
|
}
|
|
11358
|
-
|
|
11359
|
-
|
|
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
|
|
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
|
|
11517
|
-
if (
|
|
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
|
-
|
|
11679
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
21301
|
-
|
|
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
|
|
21396
|
-
function lastMine(messages
|
|
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
|
|
21432
|
-
const
|
|
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(
|
|
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.
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
-
|
|
34484
|
-
|
|
34485
|
-
|
|
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
|
|
35600
|
-
|
|
35601
|
-
const
|
|
35602
|
-
|
|
35603
|
-
const
|
|
35604
|
-
|
|
35605
|
-
|
|
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)
|
|
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)
|
|
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
|
-
/**
|
|
37172
|
-
|
|
37173
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
38516
|
-
if (!
|
|
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(
|
|
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-
|
|
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-
|
|
42577
|
+
//# sourceMappingURL=server-D5FBZYW8.js.map
|