@ghostlytools/cli 1.2.1 → 1.2.2
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 +4 -2
- package/SKILL.md +2 -1
- package/dist/assets/{arkade-CtCpFaF7.js → arkade-_gS9wShF.js} +2 -2
- package/dist/assets/{arkade-CtCpFaF7.js.map → arkade-_gS9wShF.js.map} +1 -1
- package/dist/assets/{hyperdhtRelay-C0XRWySP.js → hyperdhtRelay-CgYLaQz3.js} +3 -3
- package/dist/assets/{hyperdhtRelay-C0XRWySP.js.map → hyperdhtRelay-CgYLaQz3.js.map} +1 -1
- package/dist/assets/{mainline-DVYdrus6.js → mainline-1v8OTYvp.js} +2 -2
- package/dist/assets/{mainline-DVYdrus6.js.map → mainline-1v8OTYvp.js.map} +1 -1
- package/dist/assets/{server-D5FBZYW8.js → server-Cy4ox7oD.js} +315 -78
- package/dist/assets/server-Cy4ox7oD.js.map +1 -0
- package/dist/assets/{src-aKGjb_yz.js → src-XzcHzneo.js} +4974 -4795
- package/dist/assets/src-XzcHzneo.js.map +1 -0
- package/dist/assets/{usdt-CUfucwZf.js → usdt-Deo3g0Nq.js} +2 -2
- package/dist/assets/{usdt-CUfucwZf.js.map → usdt-Deo3g0Nq.js.map} +1 -1
- package/dist/ghostly.mjs +361 -76
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/assets/server-D5FBZYW8.js.map +0 -1
- package/dist/assets/src-aKGjb_yz.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import { $
|
|
2
|
+
import { $ as groupName, $c as sanitizeAvatar, $i as peekDhtMailbox, $o as HANDOFF_REQUEST, $r as isAppVersion, $s as deviceInviteJoinerParams, $t as EVM_TEST_CHAINS, A as encodeCommunityLink, Aa as readReaction, Ac as turnKeys, Ai as dialDescriptors, An as ENDPOINT, Ao as decodeControl, Ar as transferStalled, As as sealHandoffPiece, B as GroupWakeLimiter, Bc as withRequestOptions, Bi as newIdentityBinding, Bn as pairedMessageFrame, Br as pinIsNewer, C as openGroupTyping, Ca as GROUP_REACTION_FRAME, Cc as TURN_LAST_SEQUENCE, Co as RELAY_POLL_INTERVALS, Cr as readBeacon, Cs as readHandoffCancel, D as GROUP_READ_NOTE_COMMUNITY, Da as queueReaction, Dc as readTurnPacket, Di as CapsExchange, Do as parseLocalTarget, Dr as ChatFiles, Ds as readHandoffPake, E as CommunitySession, Ea as nextReactionNumber, Ec as nextTurnPosition, Eo as formatLocalTarget, Er as shouldBeHub, Es as readHandoffHello, F as knockIdentity, Fa as replySnippet, Fc as normalizeRelayUrl, Fi as IdentityExchange, Fn as parseCashuRequestPayload, Fr as typingActivity, Fs as setUpdateFrame, G as GROUP_READ_NOTE, Ga as checkStatusCard, Gi as emptyProofLedger, Go as HANDOFF_ACK, Gr as deviceWakeRequest, Gs as enrollDoneFrame, H as groupWakes, Hi as revokesIdentity, Hn as engineText, Ho as HTTP_SERVICE_PROTO, Hr as readPin, Hs as enrollCancelFrame, I as knockRecords, Ia as BUTTON_ID, In as parseFedimintRequestPayload, Is as signTombstone, Ja as arrivalKey, Jc as identityFromSeed, Ji as automaticTransport, Jn as groupFileFallback, Jo as HANDOFF_HAVE, Jr as relayRequest, Js as enrollProofFrame, Jt as encodeDidDhtDeactivated, K as GroupSession, Ka as sameButtonText, Ki as validExternalKey, Ko as HANDOFF_CANCEL, Kr as newWakeToken, Ks as enrollGrantFrame, Kt as didDhtDocument, L as mergeKnocks, Lc as traceLink, Li as emptyIdentityLedger, Ln as GhostLink, Lr as PIN_LIMITS, Ls as verifySetUpdate, M as decodeGroupEntryLink, Ma as wireReaction, Mc as DEFAULT_RELAYS, Mn as fedimintRequestPayload, N as encodeGroupEntryLink, Na as pairedReplyAuthor, Nc as RelayTransport, Ni as IDENTITY_REVOCATION_LABEL, Nn as findEndpoint, No as RTC_CONFIG, Nr as TypingSender, Ns as readSetUpdate, O as communityHasFrame, Oa as reactionEmoji, Oc as signTurnPacket, Oi as DHT_TEXT_CAPABILITY, Oo as webLocalFetch, Or as FILE_LIMITS, Os as readHandoffTurnFrame, P as entryParams, Pa as readReply, Pc as currentRelays, Pn as isFederationId, Ps as setAckFrame, Q as mentionsMember, Qa as shownTime, Qi as DHT_MESSAGE_TTL, Qo as HANDOFF_PIECE_BYTES, Qr as chatAppId, Qs as createDeviceInvite, Qt as ETHEREUM_USDT, R as readKnocks, Rc as budgetRetryMs, Ri as identityRevocationValue, Rn as INVITE_TAKEN, Rr as mayPin, S as groupTypingEpoch, Sa as COMMUNITY_REACTION_FRAME, Sc as turnPutSummary, Sr as rankHubs, Ss as readHandoffAck, T as COMMUNITY_LIMITS, Ta as ReactionWindow, Tc as firstDeviceSetSecret, To as GhostlyHttpError, Tr as readLobby, Ts as readHandoffHave, U as parseGroupWakeFrame, Uc as createRelayPayload, Ui as lastSharedWithMe, Un as GROUP_FILE_LIMITS, Uo as LEGACY_SERVICES, Ur as WAKE_CALL_INTERVAL_MS, Us as enrollCancelReason, V as groupWakeFrame, Va as STATUS_CARD_LIMITS, Vc as PacketTooLargeError, Vi as receivedIdentityStatus, Vn as engineError, Vo as emptyLinkRecords, Vr as pinNumberHolds, Wi as PeerProofs, Wo as serviceIdFromName, Wr as WakeLimiter, Ws as enrollDigits, X as carryMentions, Xa as heardTime, Xc as publicKeyFromZ32, Xn as readGroupFileMeta, Xo as HANDOFF_OFFER, Xr as checkPushEndpoint, Xs as readEnrollHello, Y as GROUP_EDIT_TEXT_BYTES, Ya as claimedTime, Yc as identityFromSeedB64, Yo as HANDOFF_HELLO, Yr as wakeRequest, Ys as readEnrollGrant, Yt as encodeDidDhtPacket, Z as parseCommunityEdit, Za as receivedTimestamp, Zc as verify, Zo as HANDOFF_PAKE, Zr as vapidKeysMatch, Zs as verifyEnrollProof, Zt as signDidDhtPacket, _a as RateWindow, _c as webCryptoSigner, _o as rosterHas, _r as lobbyRecords, _s as handoffReleaseFrame, a as planAppUpdate, aa as safeBlobType, ai as HOLD_CAPABILITY, al as utf8Decode, an as USDT_PUBLIC_RPC, ao as isAppRef, ar as groupSignalFrame, as as handoffAttemptSucceeded, ba as forwardedAgain, bc as TURN_SETTLE_MS, bo as DirectPathWatch, br as newerHead, bs as handoffVersions, bt as normalizeAtprotoHandle, cc as ENROLL_CAPABILITY, ci as HoldKeys, cn as assertWholeSats, cr as mayBeHub, cs as handoffContext, d as APP_BUNDLE_LIMITS, da as fileMessageText, dc as deviceEchoNonce, di as newMailbox, dn as walletNetworkOf, dr as COMMUNITY_TOPOLOGY, ds as handoffDigest, ea as DHT_FILE_CAPABILITY, ec as readDeviceInvite, ei as APPS_CAPABILITY, el as bytesEqual, en as ONCHAIN_PROVIDER, eo as APP_STATEMENT_LIMITS, er as CarriedTransport, es as HANDOFF_TIMINGS, f as APP_BUNDLE_MAGIC, fa as parseVoiceMeta, fc as deviceKeyZ32, fi as readManifest, fn as SPARK_NETWORKS, fo as readCanonicalJson, fr as beaconKeys, fs as handoffEphemeral, ga as EDIT_RATE_WINDOW_MS, gc as seedSigner, gn as isBitcoinAddress, go as commitHash, gr as lobbyKeys, gs as handoffPakeFrame, h as appViewOf, ha as EDIT_BUFFER_MS, hc as newDeviceSetSecret, hn as sparkInvoiceDetails, ho as MEMBER_KEY, hr as freshHubs, hs as handoffOfferFrame, i as checkAppBeforeRun, ia as formatFileSize, ic as encodeInviteCode, ii as UPGRADE_CAPABILITY, il as toZ32, in as TEST_USDT_FAUCET_AMOUNT, io as isAppKey, is as handoffAttemptFailed, ja as validReactionNumber, jc as turnName, jn as cashuRequestPayload, jo as encodeControl, k as decodeCommunityLink, ka as reactionIsNewer, kc as signTurnRelease, ki as capsDescriptors, kn as decodeBolt11, ko as LIMITS, kr as transferEnded, ks as readHandoffVerified, l as readAppStoreSteps, lc as HANDOFF_CAPABILITY, li as HoldRefusedError, ln as formatPaymentAmount, lr as meshHubs, ls as handoffContextProof, m as appDigest, ma as parseVideoMeta, mc as devicePingFrame, mn as sparkAddressKind, mr as doorHubs, ms as handoffManifestFrames, n as appStoreDecision, na as parseImageMeta, nc as createChatInvite, ni as EDIT_CAPABILITY, nl as randomBytes, nn as SEPOLIA_TEST_USDT, no as appRef, ns as filePartName, o as APP_STORE_LIMITS, oa as sanitizeFileName, oc as inviteOwnership, oi as HOLD_DATED_BACK_MS, ol as utf8Encode, on as WALLET_NETWORKS, oo as readAppRevocation, or as readGroupSignal, os as handoffBusyFrame, pc as deviceLinkPairing, pn as SPARK_PROVIDER, po as GROUP_MEMBER_CAP, pr as beaconRecords, ps as handoffHelloFrame, qa as statusCardText, qc as createIdentity, qi as TRANSPORTS, qo as HANDOFF_DONE, qr as parseWakeFrame, qs as enrollHelloFrame, qt as didDhtFromPublicKey, r as appUpdateDecision, rc as decodeInviteCode, ri as STATUS_CARD_CAPABILITY, rl as toBase64Url, rs as handoffAttemptAllowed, sa as sanitizeMime, sc as DEVICES_CAPABILITY, si as HOLD_LIMITS, sn as assertTokenUnits, sr as MESH_HUBS, ss as handoffCancelFrame, t as appRevocationFor, ta as dhtFileId, tc as OwnInviteError, ti as BUTTONS_CAPABILITY, tl as fromBase64Url, tn as PaymentPreflightError, to as appFingerprint, ts as HANDOFF_VERIFIED, uc as deviceEchoFrame, ui as isHoldUrl, un as validatePaymentTarget, uo as canonicalJson, ur as pickMeshHubs, us as handoffDataFrame, v as isAppUrl, va as dhtEditId, vn as isPubkyKey, vo as verifyCommitSignature, vr as mergeBeacon, vs as handoffRetryAfter, w as sealGroupTyping, wa as REACTION_LIMITS, wc as TURN_MAX, wo as presenceSeenAt, wr as readBeaconHead, ws as readHandoffDone, x as GroupTypingBoard, xa as readForwarded, xc as classifyTurnRead, xo as DHT_POLL_INTERVALS, xr as pickHubs, xs as newHandoffId, y as readAppBundle, ya as parseLinkPreview, yc as TURN_RAISE_READ_TIMEOUT_MS, yo as edgeParams, yr as mergeLobby, ys as handoffStreamKey, yt as isAtprotoDid, zc as isDiscoveryBudgetError, zi as identityStatement, zn as LIVENESS_PING_MS, zo as parseCallSignal, zr as nextPinNumber } from "./src-XzcHzneo.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-Deo3g0Nq.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,9 @@ 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-_gS9wShF.js");
|
|
646
|
+
/** How long a Testnet wallet about to make way for another waits for its server to say what it holds. */
|
|
647
|
+
var RETIRE_READ_MS = 1e4;
|
|
646
648
|
/** Every new profile starts with this wallet: nothing to set up before receiving. */
|
|
647
649
|
var DEFAULT_ARK = {
|
|
648
650
|
network: "bitcoin",
|
|
@@ -1048,10 +1050,23 @@ var ArkWallet = class {
|
|
|
1048
1050
|
async retirable(refusal) {
|
|
1049
1051
|
const saved = this.saved;
|
|
1050
1052
|
if ((await wrap((await store(STORES$1.intents, "readonly")).getAll())).some((i) => this.ours(i))) throw new Error(refusal);
|
|
1051
|
-
if (this.adapter && await this.gate.within(this.adapter
|
|
1053
|
+
if (this.adapter && await this.gate.within(this.held(this.adapter)) > 0) throw new Error(refusal);
|
|
1052
1054
|
await this.lock();
|
|
1053
1055
|
return saved;
|
|
1054
1056
|
}
|
|
1057
|
+
/**
|
|
1058
|
+
* What the wallet holds, asked of its server. A test network's server that stopped answering must not hold the
|
|
1059
|
+
* wallet on it (one that never answered does not either): past RETIRE_READ_MS the last balance read stands.
|
|
1060
|
+
* Mainnet waits for its server's own word.
|
|
1061
|
+
*/
|
|
1062
|
+
held(adapter) {
|
|
1063
|
+
const asked = adapter.balance();
|
|
1064
|
+
if (this.network !== "testnet") return asked;
|
|
1065
|
+
return new Promise((resolve, reject) => {
|
|
1066
|
+
const timer = setTimeout(() => resolve(this.view.balance), RETIRE_READ_MS);
|
|
1067
|
+
asked.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1055
1070
|
/** A payment of this wallet's network (the other network's wallet has its own). */
|
|
1056
1071
|
ours(intent) {
|
|
1057
1072
|
return intent.review.method === "arkade" && arkMode(intent.review.network) === this.network;
|
|
@@ -1876,13 +1891,24 @@ var BarkWallet = class {
|
|
|
1876
1891
|
async retirable(refusal) {
|
|
1877
1892
|
const saved = this.saved;
|
|
1878
1893
|
if ((await intentRepository.list()).some((i) => this.ours(i))) throw new Error(refusal);
|
|
1879
|
-
if (this.adapter)
|
|
1880
|
-
const b = await this.gate.within(this.adapter.balance());
|
|
1881
|
-
if (b.spendableSats + b.pendingInRoundSats + b.pendingBoardSats + b.pendingExitSats + b.pendingLightningSendSats + b.claimableLightningReceiveSats > 0) throw new Error(refusal);
|
|
1882
|
-
}
|
|
1894
|
+
if (this.adapter && await this.gate.within(this.held(this.adapter)) > 0) throw new Error(refusal);
|
|
1883
1895
|
await this.lock();
|
|
1884
1896
|
return saved;
|
|
1885
1897
|
}
|
|
1898
|
+
/**
|
|
1899
|
+
* What the wallet holds, asked of its server: spendable, on its way in and on its way out. A test network's server
|
|
1900
|
+
* that stopped answering must not hold the wallet on it (one that never answered does not either): past
|
|
1901
|
+
* RETIRE_READ_MS the last reading stands, whose three sums are those same six parts. Mainnet waits for its
|
|
1902
|
+
* server's own word.
|
|
1903
|
+
*/
|
|
1904
|
+
held(adapter) {
|
|
1905
|
+
const asked = adapter.balance().then((b) => b.spendableSats + b.pendingInRoundSats + b.pendingBoardSats + b.pendingExitSats + b.pendingLightningSendSats + b.claimableLightningReceiveSats);
|
|
1906
|
+
if (this.network !== "testnet") return asked;
|
|
1907
|
+
return new Promise((resolve, reject) => {
|
|
1908
|
+
const timer = setTimeout(() => resolve(this.view.balance + (this.view.pending ?? 0) + (this.view.exiting ?? 0)), RETIRE_READ_MS);
|
|
1909
|
+
asked.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1886
1912
|
/** A payment of this wallet's network (the other network's wallet has its own). */
|
|
1887
1913
|
ours(intent) {
|
|
1888
1914
|
return intent.review.method === "bark" && barkMode(intent.review.network) === this.network;
|
|
@@ -11145,6 +11171,11 @@ var APP_CHECK_TIMINGS = {
|
|
|
11145
11171
|
};
|
|
11146
11172
|
/** Revocations are checked this long at a time before the page's thread is let go. */
|
|
11147
11173
|
var REVOCATIONS_SLICE_MS = 10;
|
|
11174
|
+
/**
|
|
11175
|
+
* Revocations of store indexes whose signature was checked, kept by `appRevocationId` so an index that changes around
|
|
11176
|
+
* them does not have each checked again (the one read longest ago dropped first): four indexes' worth, about 1 MiB.
|
|
11177
|
+
*/
|
|
11178
|
+
var STORE_REVOCATIONS_KEPT = 4 * APP_STORE_LIMITS.revoked;
|
|
11148
11179
|
/** `ghostly-revoke.json` files whose result is kept, so an unchanged one is not checked again (the oldest dropped first). */
|
|
11149
11180
|
var REVOCATION_FILES_KEPT = 64;
|
|
11150
11181
|
/** A fetched bundle kept for its install screen: at most this many, for this long. */
|
|
@@ -11231,7 +11262,7 @@ var Apps = class {
|
|
|
11231
11262
|
/** Reads of a bundle under way, by digest: icons drawn at once share one read. */
|
|
11232
11263
|
verifying = /* @__PURE__ */ new Map();
|
|
11233
11264
|
/**
|
|
11234
|
-
* The `icon.png` of each checked bundle, by digest (at most 256 KiB, or null for none): every icon shown on the Apps
|
|
11265
|
+
* The `icon.png` of each checked bundle, by digest (at most 256 KiB and 1024 × 1024 pixels, or null for none): every icon shown on the Apps
|
|
11235
11266
|
* page, in a chat's cards and in the composer's Apps is read without the whole bundle again.
|
|
11236
11267
|
*/
|
|
11237
11268
|
icons = /* @__PURE__ */ new Map();
|
|
@@ -11244,6 +11275,13 @@ var Apps = class {
|
|
|
11244
11275
|
checking = null;
|
|
11245
11276
|
/** Runs the person started with Run anyway (`<ref> <digest>`), for this engine's life: a removal does not cut them off. */
|
|
11246
11277
|
anyway = /* @__PURE__ */ new Set();
|
|
11278
|
+
/** The apps started here (`entry`), for this engine's life: one of them uninstalled since may still have a frame open. */
|
|
11279
|
+
ran = /* @__PURE__ */ new Set();
|
|
11280
|
+
/**
|
|
11281
|
+
* The versions started here (`entry`), by reference: digest to `sequence`, for this engine's life. A frame goes on
|
|
11282
|
+
* running the version it started with whatever is installed since, so that version is the one judged when it asks.
|
|
11283
|
+
*/
|
|
11284
|
+
started = /* @__PURE__ */ new Map();
|
|
11247
11285
|
views = null;
|
|
11248
11286
|
held = /* @__PURE__ */ new Map();
|
|
11249
11287
|
heldStores = null;
|
|
@@ -11254,6 +11292,19 @@ var Apps = class {
|
|
|
11254
11292
|
listingMisses = /* @__PURE__ */ new Map();
|
|
11255
11293
|
/** `ghostly-revoke.json` files checked, by the SHA-256 of their bytes: their revocations, or null when refused. */
|
|
11256
11294
|
revocationFiles = /* @__PURE__ */ new Map();
|
|
11295
|
+
/** Revocations read in a store's index and verified, by `appRevocationId`: at most `STORE_REVOCATIONS_KEPT`. */
|
|
11296
|
+
storeRevocations = /* @__PURE__ */ new Set();
|
|
11297
|
+
checkedRevocations = {
|
|
11298
|
+
has: (id) => {
|
|
11299
|
+
if (!this.storeRevocations.delete(id)) return false;
|
|
11300
|
+
this.storeRevocations.add(id);
|
|
11301
|
+
return true;
|
|
11302
|
+
},
|
|
11303
|
+
add: (id) => {
|
|
11304
|
+
this.storeRevocations.add(id);
|
|
11305
|
+
if (this.storeRevocations.size > STORE_REVOCATIONS_KEPT) this.storeRevocations.delete(this.storeRevocations.values().next().value);
|
|
11306
|
+
}
|
|
11307
|
+
};
|
|
11257
11308
|
stopped = false;
|
|
11258
11309
|
constructor(host) {
|
|
11259
11310
|
this.host = host;
|
|
@@ -11337,22 +11388,48 @@ var Apps = class {
|
|
|
11337
11388
|
* a store of the person's removed (unless this run was started with Run anyway), is cut off with `stopped`, whether
|
|
11338
11389
|
* or not the page stops its frame.
|
|
11339
11390
|
*/
|
|
11340
|
-
async stillRunnable(app) {
|
|
11341
|
-
const
|
|
11391
|
+
async stillRunnable(app, digest) {
|
|
11392
|
+
const version = this.versionRunning(app, digest);
|
|
11393
|
+
const run = this.runStatus(version, await this.heldViews(), app.revocations);
|
|
11342
11394
|
if (run.status === "revoked") fail("stopped", "Its publisher revoked this version");
|
|
11343
|
-
if (run.status === "removed" && !this.anyway.has(`${
|
|
11395
|
+
if (run.status === "removed" && !this.anyway.has(`${version.ref} ${version.digest}`)) fail("stopped", `Removed by ${run.by[0].name}: ${run.by[0].reason}`);
|
|
11344
11396
|
return app;
|
|
11345
11397
|
}
|
|
11346
|
-
|
|
11347
|
-
|
|
11398
|
+
/**
|
|
11399
|
+
* The version a frame runs: the one it names by `digest` (what `entry` gave it), which an update installed since does
|
|
11400
|
+
* not change. None named: the installed one. One never started here does not run.
|
|
11401
|
+
*/
|
|
11402
|
+
versionRunning(app, digest) {
|
|
11403
|
+
if (digest === void 0 || digest === app.digest) return app;
|
|
11404
|
+
const sequence = typeof digest === "string" ? this.started.get(app.ref)?.get(digest) : void 0;
|
|
11405
|
+
if (sequence === void 0) fail("stopped", "This version is not running");
|
|
11406
|
+
return {
|
|
11407
|
+
ref: app.ref,
|
|
11408
|
+
sequence,
|
|
11409
|
+
digest
|
|
11410
|
+
};
|
|
11411
|
+
}
|
|
11412
|
+
/** The versions started here that are not the installed one any more. */
|
|
11413
|
+
startedBefore(app) {
|
|
11414
|
+
return [...this.started.get(app.ref) ?? []].filter(([digest]) => digest !== app.digest).map(([digest, sequence]) => ({
|
|
11415
|
+
ref: app.ref,
|
|
11416
|
+
sequence,
|
|
11417
|
+
digest
|
|
11418
|
+
}));
|
|
11419
|
+
}
|
|
11420
|
+
async runningOrFail(ref, digest) {
|
|
11421
|
+
return this.stillRunnable(await this.installedOrFail(ref), digest);
|
|
11348
11422
|
}
|
|
11349
11423
|
/**
|
|
11350
11424
|
* Before an app's frames go to a contact (`appOpen`, `appSend`): refused with `stopped` as its storage is. A reference
|
|
11351
|
-
* not installed here (a bot that speaks an app's messages) is not this store's to refuse
|
|
11425
|
+
* not installed here (a bot that speaks an app's messages) is not this store's to refuse, unless it ran here and was
|
|
11426
|
+
* uninstalled since: the person removed it, so a frame of it still open says nothing more (`not-installed`). `digest`:
|
|
11427
|
+
* the version the frame runs (`versionRunning`).
|
|
11352
11428
|
*/
|
|
11353
|
-
async chatRunnable({ ref }) {
|
|
11429
|
+
async chatRunnable({ ref, digest }) {
|
|
11354
11430
|
const app = isAppRef(ref) ? await this.app(ref) : void 0;
|
|
11355
|
-
if (app
|
|
11431
|
+
if (!app && this.ran.has(ref)) fail("not-installed", "This app is not installed");
|
|
11432
|
+
if (app) await this.stillRunnable(app, digest);
|
|
11356
11433
|
}
|
|
11357
11434
|
async putApp(app) {
|
|
11358
11435
|
try {
|
|
@@ -11543,7 +11620,10 @@ var Apps = class {
|
|
|
11543
11620
|
}
|
|
11544
11621
|
/**
|
|
11545
11622
|
* Reads the index at `url`. With the index held for this store, bytes equal to it (by digest) are the held index: its
|
|
11546
|
-
* signature and every revocation in it were checked when it was read, so only its expiry is taken again.
|
|
11623
|
+
* signature and every revocation in it were checked when it was read, so only its expiry is taken again. Any other
|
|
11624
|
+
* index is read with a pause every few ms (the engine shares the page's thread, and an index holds up to 4096
|
|
11625
|
+
* listings and 4096 revocations, each of these a signature check), and a revocation checked in an earlier read, of
|
|
11626
|
+
* this store or another, a preview included, is not checked again.
|
|
11547
11627
|
*/
|
|
11548
11628
|
async readStoreAt(url, heldKey, held) {
|
|
11549
11629
|
const [indexBytes, sigBytes] = await Promise.all([this.host.fetch(url, { maxBytes: APP_FETCH_LIMITS.storeIndexBytes }), this.host.fetch(besideUrl(url, "ghostly-store.sig"), { maxBytes: APP_FETCH_LIMITS.sigBytes })]);
|
|
@@ -11555,7 +11635,16 @@ var Apps = class {
|
|
|
11555
11635
|
expired: held.index.expires < this.nowS()
|
|
11556
11636
|
}
|
|
11557
11637
|
};
|
|
11558
|
-
|
|
11638
|
+
const steps = readAppStoreSteps(indexBytes, sigBytes, this.nowS(), heldKey, this.checkedRevocations);
|
|
11639
|
+
let since = performance.now();
|
|
11640
|
+
for (;;) {
|
|
11641
|
+
const step = steps.next();
|
|
11642
|
+
if (step.done) return step.value;
|
|
11643
|
+
if (performance.now() - since >= REVOCATIONS_SLICE_MS) {
|
|
11644
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
11645
|
+
since = performance.now();
|
|
11646
|
+
}
|
|
11647
|
+
}
|
|
11559
11648
|
}
|
|
11560
11649
|
/**
|
|
11561
11650
|
* The revocations its publisher signed, read beside each URL (`ghostly-revoke.json`); none there is not an error.
|
|
@@ -12142,7 +12231,11 @@ var Apps = class {
|
|
|
12142
12231
|
}
|
|
12143
12232
|
const after = await this.readRevocations(app.ref, [app.from, ...app.manifest.sources ?? []], revokeRead);
|
|
12144
12233
|
const revocations = before || after ? [...before ?? [], ...after ?? []] : null;
|
|
12145
|
-
const versions = [
|
|
12234
|
+
const versions = [
|
|
12235
|
+
app,
|
|
12236
|
+
...app.pending ? [versionOf(app.pending.manifest, app.pending.digest)] : [],
|
|
12237
|
+
...this.startedBefore(app)
|
|
12238
|
+
];
|
|
12146
12239
|
const stays = (app.revocations ?? []).filter((r) => !revocations || versions.some((v) => appRevocationFor(v, [r])));
|
|
12147
12240
|
const merged = new Map([...stays, ...revocations ?? []].map((r) => [canonicalJson(r), r]));
|
|
12148
12241
|
if ((await this.app(app.ref))?.store === void 0 && app.store !== void 0) {
|
|
@@ -12233,6 +12326,9 @@ var Apps = class {
|
|
|
12233
12326
|
if (run.status === "removed") this.anyway.add(`${app.ref} ${app.digest}`);
|
|
12234
12327
|
const bundle = await this.verifiedBundle(app);
|
|
12235
12328
|
const entry = bundle.files.get(bundle.manifest.entry) ?? fail("damaged", "The entry is missing");
|
|
12329
|
+
this.ran.add(app.ref);
|
|
12330
|
+
const versions = this.started.get(app.ref) ?? /* @__PURE__ */ new Map();
|
|
12331
|
+
this.started.set(app.ref, versions.set(app.digest, app.sequence));
|
|
12236
12332
|
return {
|
|
12237
12333
|
ref: app.ref,
|
|
12238
12334
|
digest: app.digest,
|
|
@@ -12243,9 +12339,10 @@ var Apps = class {
|
|
|
12243
12339
|
entry: utf8Decode(entry)
|
|
12244
12340
|
};
|
|
12245
12341
|
}
|
|
12246
|
-
/** A file of the installed bundle, for the broker's `file` (`ghostly.file(path)`). */
|
|
12247
|
-
async file({ ref, path }) {
|
|
12248
|
-
const app = path === "icon.png" ? await this.installedOrFail(ref) : await this.runningOrFail(ref);
|
|
12342
|
+
/** A file of the installed bundle, for the broker's `file` (`ghostly.file(path)`). `digest`: the version the frame runs. */
|
|
12343
|
+
async file({ ref, path, digest }) {
|
|
12344
|
+
const app = path === "icon.png" ? await this.installedOrFail(ref) : await this.runningOrFail(ref, digest);
|
|
12345
|
+
if (path !== "icon.png" && digest !== void 0 && digest !== app.digest) fail("no-file", "No such file in this app");
|
|
12249
12346
|
const icon = path === "icon.png" ? this.icons.get(app.digest) : void 0;
|
|
12250
12347
|
if (icon) return icon.slice();
|
|
12251
12348
|
if (icon === null) fail("no-file", "No such file in this app");
|
|
@@ -12256,6 +12353,10 @@ var Apps = class {
|
|
|
12256
12353
|
async view(app, stores) {
|
|
12257
12354
|
const { manifest } = app;
|
|
12258
12355
|
const listedBy = this.listedBy(app.ref, stores);
|
|
12356
|
+
const stopped = this.startedBefore(app).map((v) => ({
|
|
12357
|
+
digest: v.digest,
|
|
12358
|
+
run: this.runStatus(v, this.storeViews(stores), app.revocations)
|
|
12359
|
+
})).filter((v) => v.run.status !== "ok");
|
|
12259
12360
|
return {
|
|
12260
12361
|
ref: app.ref,
|
|
12261
12362
|
name: app.name,
|
|
@@ -12285,7 +12386,8 @@ var Apps = class {
|
|
|
12285
12386
|
sequence: app.equivocation.sequence,
|
|
12286
12387
|
at: app.equivocation.at
|
|
12287
12388
|
} },
|
|
12288
|
-
...app.checkedAt !== void 0 && { checkedAt: app.checkedAt }
|
|
12389
|
+
...app.checkedAt !== void 0 && { checkedAt: app.checkedAt },
|
|
12390
|
+
...stopped.length && { stopped }
|
|
12289
12391
|
};
|
|
12290
12392
|
}
|
|
12291
12393
|
async list() {
|
|
@@ -12307,6 +12409,7 @@ var Apps = class {
|
|
|
12307
12409
|
this.held.delete(app.ref);
|
|
12308
12410
|
}
|
|
12309
12411
|
for (const key of [...this.usage.keys()]) if (key.startsWith(`${app.ref}\0`)) this.usage.delete(key);
|
|
12412
|
+
this.started.delete(app.ref);
|
|
12310
12413
|
await this.removeBundle(app.bytes, app.digest);
|
|
12311
12414
|
if (app.pending) await this.removeBundle(app.pending.bytes, app.pending.digest);
|
|
12312
12415
|
}
|
|
@@ -12337,8 +12440,8 @@ var Apps = class {
|
|
|
12337
12440
|
}
|
|
12338
12441
|
return used;
|
|
12339
12442
|
}
|
|
12340
|
-
async storageGet({ ref, scope, key }) {
|
|
12341
|
-
const app = await this.runningOrFail(ref);
|
|
12443
|
+
async storageGet({ ref, scope, key, digest }) {
|
|
12444
|
+
const app = await this.runningOrFail(ref, digest);
|
|
12342
12445
|
const row = await wrap((await store(STORES$1.appStorage, "readonly")).get([
|
|
12343
12446
|
app.ref,
|
|
12344
12447
|
this.scopeOf(scope),
|
|
@@ -12347,8 +12450,8 @@ var Apps = class {
|
|
|
12347
12450
|
return row ? { value: JSON.parse(row.value) } : null;
|
|
12348
12451
|
}
|
|
12349
12452
|
/** Keeps a JSON value under a key: at most 64 KiB, and 5 MiB for every key of this app in this scope. */
|
|
12350
|
-
async storageSet({ ref, scope, key, value }) {
|
|
12351
|
-
const app = await this.runningOrFail(ref);
|
|
12453
|
+
async storageSet({ ref, scope, key, value, digest }) {
|
|
12454
|
+
const app = await this.runningOrFail(ref, digest);
|
|
12352
12455
|
const where = this.scopeOf(scope), name = this.keyOf(key);
|
|
12353
12456
|
let text;
|
|
12354
12457
|
try {
|
|
@@ -12384,8 +12487,8 @@ var Apps = class {
|
|
|
12384
12487
|
this.usage.set(`${app.ref}\0${where}`, next);
|
|
12385
12488
|
});
|
|
12386
12489
|
}
|
|
12387
|
-
async storageDelete({ ref, scope, key }) {
|
|
12388
|
-
const app = await this.runningOrFail(ref);
|
|
12490
|
+
async storageDelete({ ref, scope, key, digest }) {
|
|
12491
|
+
const app = await this.runningOrFail(ref, digest);
|
|
12389
12492
|
const where = this.scopeOf(scope), name = this.keyOf(key);
|
|
12390
12493
|
await this.serial(`${app.ref}\0${where}`, async () => {
|
|
12391
12494
|
const used = await this.usageOf(app.ref, where);
|
|
@@ -12403,8 +12506,8 @@ var Apps = class {
|
|
|
12403
12506
|
this.usage.set(`${app.ref}\0${where}`, used - old.size);
|
|
12404
12507
|
});
|
|
12405
12508
|
}
|
|
12406
|
-
async storageKeys({ ref, scope }) {
|
|
12407
|
-
const app = await this.runningOrFail(ref);
|
|
12509
|
+
async storageKeys({ ref, scope, digest }) {
|
|
12510
|
+
const app = await this.runningOrFail(ref, digest);
|
|
12408
12511
|
return (await wrap((await store(STORES$1.appStorage, "readonly")).getAllKeys(rowsOf(app.ref, this.scopeOf(scope))))).map((k) => k[2]);
|
|
12409
12512
|
}
|
|
12410
12513
|
/** An app's storage in every scope, as export files (WISP 1200 § Where installed apps live), offered before an uninstall. */
|
|
@@ -21699,13 +21802,16 @@ function pinOfFormerMember(pin, inRoster) {
|
|
|
21699
21802
|
function mayPinIn(group) {
|
|
21700
21803
|
return !!group?.myKey && group.status === "active" && mayPin(group.profile, group.myKey, group.members.find((m) => m.role === "admin")?.key);
|
|
21701
21804
|
}
|
|
21702
|
-
/** The row a pin names, when it is here: the row of that id (a group's), or of `peer_`/`me_` and that id, or any other. */
|
|
21805
|
+
/** The row a pin names, when it is here: the row of that id (a group's), or of `peer_`/`me_` and that id, or any other of a chat's. */
|
|
21703
21806
|
async function pinnedRow(host, chat, id) {
|
|
21704
21807
|
if (!id) return void 0;
|
|
21705
21808
|
const group = isGroup(chat);
|
|
21706
|
-
if (host.message)
|
|
21707
|
-
const
|
|
21708
|
-
|
|
21809
|
+
if (host.message) {
|
|
21810
|
+
for (const rowId of group ? [id] : [`peer_${id}`, `me_${id}`]) {
|
|
21811
|
+
const row = await host.message(chat, rowId);
|
|
21812
|
+
if (row && replyRef(row, group) === id) return row;
|
|
21813
|
+
}
|
|
21814
|
+
if (group) return void 0;
|
|
21709
21815
|
}
|
|
21710
21816
|
return reactionTarget(await host.messages(chat), id, group);
|
|
21711
21817
|
}
|
|
@@ -21930,6 +22036,17 @@ var FAREWELL_RETRY_MS = 6e4;
|
|
|
21930
22036
|
var FAREWELL_TOLD_MS = 6e5;
|
|
21931
22037
|
var FAREWELL_SAY_MS = 1e4;
|
|
21932
22038
|
/**
|
|
22039
|
+
* A key asking in my lobby that no commit I hold names may be a member let in by a commit I have not heard yet: a hub
|
|
22040
|
+
* keeps an edge to it for a moment, on which it says where it is and takes commits (`wantAhead`). At most `AHEAD_EDGES`
|
|
22041
|
+
* at a time, each awaited `AHEAD_WAIT_MS` and closed `AHEAD_LINGER_MS` after it came up unless a commit named the key
|
|
22042
|
+
* by then; the same key is not waited for again for a while (longer once its edge was up).
|
|
22043
|
+
*/
|
|
22044
|
+
var AHEAD_EDGES = 4;
|
|
22045
|
+
var AHEAD_WAIT_MS = 12e4;
|
|
22046
|
+
var AHEAD_LINGER_MS = 2e4;
|
|
22047
|
+
var AHEAD_RETRY_MS = 6e4;
|
|
22048
|
+
var AHEAD_HEARD_MS = 6e5;
|
|
22049
|
+
/**
|
|
21933
22050
|
* A key asking from before the commit that took it out was there and was sent that commit: not waited for. The two
|
|
21934
22051
|
* times are two devices' clocks (the asker's, the commit's signer's), so "before" allows this much between them:
|
|
21935
22052
|
* a returning member whose clock runs behind the admin's is told all the same.
|
|
@@ -22401,6 +22518,7 @@ var Communities = class {
|
|
|
22401
22518
|
await this.publishBeacon(group.id, live, now, false).catch(() => {});
|
|
22402
22519
|
}
|
|
22403
22520
|
live.farewells.clear();
|
|
22521
|
+
live.ahead.clear();
|
|
22404
22522
|
await this.reconcile(group.id, live, now);
|
|
22405
22523
|
continue;
|
|
22406
22524
|
}
|
|
@@ -22494,6 +22612,7 @@ var Communities = class {
|
|
|
22494
22612
|
if (this.leftItself(live, h.key) ? h.ts - FAREWELL_SKEW_MS > out : h.ts + FAREWELL_SKEW_MS > out) this.wantFarewell(groupId, live, h.key, now);
|
|
22495
22613
|
}
|
|
22496
22614
|
this.endFarewells(live, now);
|
|
22615
|
+
this.endAhead(groupId, live, now);
|
|
22497
22616
|
if (live.hub) {
|
|
22498
22617
|
const busy = this.admitting(groupId, live, now);
|
|
22499
22618
|
if (s.state.pendingLeaves.some((r) => r.s !== me && this.recentHub(live, r.s))) live.lobbyBusyUntil = Math.max(live.lobbyBusyUntil, now + this.timings.lobbyBusyMs);
|
|
@@ -22572,8 +22691,9 @@ var Communities = class {
|
|
|
22572
22691
|
}
|
|
22573
22692
|
await this.keepLooking(groupId, live, now);
|
|
22574
22693
|
for (const [key, since] of live.pendingEntries) {
|
|
22575
|
-
const linkId = this.host.entries(groupId).get(key);
|
|
22576
|
-
|
|
22694
|
+
const linkId = this.host.entries(groupId).get(key), up = !!linkId && this.host.linkReady(linkId, 2);
|
|
22695
|
+
const elsewhere = rosterHas(s.roster, key) && since < this.addedAt(s, key) + this.timings.knockFallbackMs;
|
|
22696
|
+
if (!up && (elsewhere || now - since > ENTRY_UNANSWERED_MS)) live.pendingEntries.delete(key);
|
|
22577
22697
|
else if (now - since > ENTRY_TIMEOUT_MS$1) {
|
|
22578
22698
|
live.pendingEntries.delete(key);
|
|
22579
22699
|
this.refused.set(key, now + REFUSED_FOR_MS$1);
|
|
@@ -22632,7 +22752,7 @@ var Communities = class {
|
|
|
22632
22752
|
const edges = this.host.edges(groupId);
|
|
22633
22753
|
if (now - live.beaconAt <= 2 * this.timings.beaconReadMs) {
|
|
22634
22754
|
const hubs = new Set(freshHubs(this.hubs(live), now).map((h) => h.key));
|
|
22635
|
-
for (const [key, id] of edges) if (!hubs.has(key) && !live.members.has(key) && this.host.linkReady(id, 2) && !this.gone(live, key)) live.members.set(key, now);
|
|
22755
|
+
for (const [key, id] of edges) if (!hubs.has(key) && !live.members.has(key) && this.host.linkReady(id, 2) && rosterHas(live.session.roster, key) && !this.gone(live, key)) live.members.set(key, now);
|
|
22636
22756
|
for (const key of [...live.members.keys()]) if (hubs.has(key)) live.members.delete(key);
|
|
22637
22757
|
}
|
|
22638
22758
|
for (const [key, since] of live.members) {
|
|
@@ -22770,16 +22890,21 @@ var Communities = class {
|
|
|
22770
22890
|
for (let i = chain.length - 1; i >= 0; i--) if ((chain[i].k === "leave" || chain[i].k === "remove") && chain[i].s === key) return chain[i].k === "leave";
|
|
22771
22891
|
return false;
|
|
22772
22892
|
}
|
|
22773
|
-
/**
|
|
22774
|
-
|
|
22775
|
-
|
|
22893
|
+
/**
|
|
22894
|
+
* A frame goes on an edge to a member; to someone the chain took out, only the commit that took it out. What the
|
|
22895
|
+
* group says (`said`: a frame of mine for everyone, or one a hub relays) goes to the members of the roster only:
|
|
22896
|
+
* a key no commit names hears none of it, and one let in on a branch that lost hears the commits.
|
|
22897
|
+
*/
|
|
22898
|
+
mayHear(live, key, frame, said = false) {
|
|
22776
22899
|
const f = frame;
|
|
22777
|
-
return f.t === "group-commit" && (f.commit?.k === "remove" || f.commit?.k === "leave") && f.commit.s === key;
|
|
22900
|
+
if (live?.session.wasRemoved(key)) return f.t === "group-commit" && (f.commit?.k === "remove" || f.commit?.k === "leave") && f.commit.s === key;
|
|
22901
|
+
if (!live || !said || rosterHas(live.session.roster, key)) return true;
|
|
22902
|
+
return f.t === "group-commit" && live.session.isRecentMember(key);
|
|
22778
22903
|
}
|
|
22779
|
-
/** The edges of the group that may hear `frame
|
|
22904
|
+
/** The edges of the group that may hear `frame`, said to everyone. */
|
|
22780
22905
|
hearers(groupId, frame) {
|
|
22781
22906
|
const live = this.live.get(groupId);
|
|
22782
|
-
return [...this.host.edges(groupId)].filter(([key]) => this.mayHear(live, key, frame));
|
|
22907
|
+
return [...this.host.edges(groupId)].filter(([key]) => this.mayHear(live, key, frame, true));
|
|
22783
22908
|
}
|
|
22784
22909
|
/** As a hub: someone the chain took out is asking (in my lobby, or listing itself as a hub). An edge to tell it, within bounds. */
|
|
22785
22910
|
wantFarewell(groupId, live, key, now) {
|
|
@@ -22800,6 +22925,34 @@ var Communities = class {
|
|
|
22800
22925
|
for (const [key, at] of live.farewellSaid) if (now - at >= FAREWELL_SAY_MS) live.farewellSaid.delete(key);
|
|
22801
22926
|
}
|
|
22802
22927
|
/**
|
|
22928
|
+
* As a hub: a key no commit I hold names is asking in my lobby. It may be a member ahead of me on the roster, who needs
|
|
22929
|
+
* the edge for me to find out (its answer to my `group-sync` carries the commit); or nobody, since whoever was once
|
|
22930
|
+
* given the group's rendezvous secret can write a key there. An edge, within bounds, that is no member's: it takes
|
|
22931
|
+
* no place among my members, and nothing the group says goes on it (`mayHear`).
|
|
22932
|
+
*/
|
|
22933
|
+
wantAhead(groupId, live, key, now) {
|
|
22934
|
+
if (live.ahead.has(key) || now < (live.aheadQuiet.get(key) ?? 0)) return;
|
|
22935
|
+
if (live.ahead.size >= AHEAD_EDGES || this.capacity(groupId, live, now) <= 0) return;
|
|
22936
|
+
live.ahead.set(key, { since: now });
|
|
22937
|
+
if (!this.host.edges(groupId).has(key)) live.expect.add(key);
|
|
22938
|
+
}
|
|
22939
|
+
/** Those edges end: a commit named the key (a member of mine now, or out), it never came, or its moment is over; and a hub no more keeps none. */
|
|
22940
|
+
endAhead(groupId, live, now) {
|
|
22941
|
+
const edges = this.host.edges(groupId);
|
|
22942
|
+
for (const [key, a] of live.ahead) {
|
|
22943
|
+
const member = rosterHas(live.session.roster, key), named = member || live.session.wasRemoved(key);
|
|
22944
|
+
if (!named && live.hub) {
|
|
22945
|
+
const id = edges.get(key);
|
|
22946
|
+
if (!a.up && id && this.host.linkReady(id, 2)) a.up = now;
|
|
22947
|
+
if (a.up ? now - a.up < AHEAD_LINGER_MS : now - a.since < AHEAD_WAIT_MS) continue;
|
|
22948
|
+
}
|
|
22949
|
+
live.ahead.delete(key);
|
|
22950
|
+
if (!named) live.aheadQuiet.set(key, now + (a.up ? AHEAD_HEARD_MS : AHEAD_RETRY_MS));
|
|
22951
|
+
else if (member && live.hub && live.members.size < this.capacity(groupId, live, now)) live.members.set(key, now);
|
|
22952
|
+
}
|
|
22953
|
+
for (const [key, until] of live.aheadQuiet) if (until <= now) live.aheadQuiet.delete(key);
|
|
22954
|
+
}
|
|
22955
|
+
/**
|
|
22803
22956
|
* Someone the chain took out says where it is (`group-sync`): it is handed the commits up to the one that took it
|
|
22804
22957
|
* out, and nothing else (`CommunitySession.farewell`). Nothing else it says is heard.
|
|
22805
22958
|
*/
|
|
@@ -22863,6 +23016,10 @@ var Communities = class {
|
|
|
22863
23016
|
if (ts + FAREWELL_SKEW_MS > (live.session.outAt(key) ?? Infinity)) this.wantFarewell(groupId, live, key, now);
|
|
22864
23017
|
continue;
|
|
22865
23018
|
}
|
|
23019
|
+
if (!rosterHas(live.session.roster, key)) {
|
|
23020
|
+
this.wantAhead(groupId, live, key, now);
|
|
23021
|
+
continue;
|
|
23022
|
+
}
|
|
22866
23023
|
live.emptySince = now;
|
|
22867
23024
|
if (!live.members.has(key) && live.members.size >= this.capacity(groupId, live, now)) continue;
|
|
22868
23025
|
if (!live.members.has(key)) {
|
|
@@ -22883,6 +23040,7 @@ var Communities = class {
|
|
|
22883
23040
|
for (const h of freshHubs(this.hubs(live), now)) if (h.key !== s.myKey && !this.hubDead(groupId, live, h, now)) wanted.add(h.key);
|
|
22884
23041
|
for (const key of live.members.keys()) wanted.add(key);
|
|
22885
23042
|
for (const key of live.farewells.keys()) wanted.add(key);
|
|
23043
|
+
for (const key of live.ahead.keys()) wanted.add(key);
|
|
22886
23044
|
} else for (const key of live.myHubs) wanted.add(key);
|
|
22887
23045
|
for (const [key, id] of existing) if (this.host.linkReady(id, 2) && (live.members.has(key) || this.recentHub(live, key)) && !s.wasRemoved(key)) wanted.add(key);
|
|
22888
23046
|
}
|
|
@@ -23003,8 +23161,7 @@ var Communities = class {
|
|
|
23003
23161
|
if (now - ts > 18e4 || this.refused.has(key) || live.pendingEntries.has(key) || key === link.host || key === s.myKey) continue;
|
|
23004
23162
|
const stillKnocking = now - ts < 2 * this.timings.slowKnockMs;
|
|
23005
23163
|
if (rosterHas(s.roster, key)) {
|
|
23006
|
-
|
|
23007
|
-
if (!stillKnocking || ts < added + this.timings.knockFallbackMs) continue;
|
|
23164
|
+
if (!stillKnocking || ts < this.addedAt(s, key) + this.timings.knockFallbackMs) continue;
|
|
23008
23165
|
}
|
|
23009
23166
|
const seen = live.knocksSeen.get(key);
|
|
23010
23167
|
const changed = !!seen && seen.doors !== doorSig;
|
|
@@ -23034,6 +23191,10 @@ var Communities = class {
|
|
|
23034
23191
|
}
|
|
23035
23192
|
for (const [key, { first }] of live.knocksSeen) if (now - first > 36e4) live.knocksSeen.delete(key);
|
|
23036
23193
|
}
|
|
23194
|
+
/** When the chain last added this member (0: never). */
|
|
23195
|
+
addedAt(s, key) {
|
|
23196
|
+
return [...s.state.chain].reverse().find((c) => c.k === "add" && c.s === key)?.ts ?? 0;
|
|
23197
|
+
}
|
|
23037
23198
|
/** Leave requests: the door commits them (see `answerKnocks`); any hub after a while. */
|
|
23038
23199
|
async commitLeaves(live, otherHubs, now) {
|
|
23039
23200
|
const s = live.session;
|
|
@@ -23280,6 +23441,7 @@ var Communities = class {
|
|
|
23280
23441
|
if (f.t === "group-sync" && live.session.isMember) this.sayFarewell(groupId, live, peerKey, f);
|
|
23281
23442
|
return;
|
|
23282
23443
|
}
|
|
23444
|
+
if (!live.session.isRecentMember(peerKey) && f.t !== "group-commit" && f.t !== "group-msg" && f.t !== "group-secrets" && !(f.t === "group-secret" && f.to === live.session.myKey)) return;
|
|
23283
23445
|
if ((f.t === "group-secret" || f.t === "group-entry") && typeof f.to === "string" && f.to !== live.session.myKey) {
|
|
23284
23446
|
if (!live.hub) return;
|
|
23285
23447
|
const id = `${String(f.t)}:${f.to}:${String(f.h ?? f.x)}`;
|
|
@@ -23334,7 +23496,7 @@ var Communities = class {
|
|
|
23334
23496
|
edgeReady(groupId, peerKey, linkId) {
|
|
23335
23497
|
const live = this.live.get(groupId);
|
|
23336
23498
|
if (!live || live.session.wasRemoved(peerKey)) return;
|
|
23337
|
-
if (live.hub && !this.peerIsHub(live, peerKey)) live.members.set(peerKey, this.now());
|
|
23499
|
+
if (live.hub && !this.peerIsHub(live, peerKey) && rosterHas(live.session.roster, peerKey)) live.members.set(peerKey, this.now());
|
|
23338
23500
|
this.sendTo(linkId, live.session.syncFrame());
|
|
23339
23501
|
this.host.emit();
|
|
23340
23502
|
}
|
|
@@ -23449,6 +23611,8 @@ var Communities = class {
|
|
|
23449
23611
|
farewells: /* @__PURE__ */ new Map(),
|
|
23450
23612
|
farewellQuiet: /* @__PURE__ */ new Map(),
|
|
23451
23613
|
farewellSaid: /* @__PURE__ */ new Map(),
|
|
23614
|
+
ahead: /* @__PURE__ */ new Map(),
|
|
23615
|
+
aheadQuiet: /* @__PURE__ */ new Map(),
|
|
23452
23616
|
knocksScanned: false,
|
|
23453
23617
|
knockCursor: 0,
|
|
23454
23618
|
lastShardPoll: 0,
|
|
@@ -24653,6 +24817,8 @@ var Groups = class {
|
|
|
24653
24817
|
removedAt = /* @__PURE__ */ new Map();
|
|
24654
24818
|
/** Who is typing in each private group, and this side's word there (WISP 902 · Group Mesh § Typing). */
|
|
24655
24819
|
typings;
|
|
24820
|
+
/** A member's pins taken per window in a private group (group and member key). */
|
|
24821
|
+
pinPaces = /* @__PURE__ */ new Map();
|
|
24656
24822
|
/** Edge signaling through members (WISP 902 · Group Mesh § Signaling through members). */
|
|
24657
24823
|
signals;
|
|
24658
24824
|
/** Files and voice messages in every group, private or community (WISP 503 · Group Files). */
|
|
@@ -24718,6 +24884,13 @@ var Groups = class {
|
|
|
24718
24884
|
if (!asItIs) await this.files?.sweep(all.map((g) => g.id));
|
|
24719
24885
|
for (const group of this.stored.values()) for (const [, linkId] of this.host.entries(group.id)) if (!group.invitation?.entry || group.invitation.linkId !== linkId) await this.host.closeEdge(linkId);
|
|
24720
24886
|
}
|
|
24887
|
+
/** The pins taken per window from one member's edge in a private group, as a paired chat's session limits them. */
|
|
24888
|
+
pinPace(groupId, member) {
|
|
24889
|
+
const key = `${groupId}\n${member}`;
|
|
24890
|
+
let pace = this.pinPaces.get(key);
|
|
24891
|
+
if (!pace) this.pinPaces.set(key, pace = new ReactionWindow(PIN_LIMITS.receive, PIN_LIMITS.windowMs));
|
|
24892
|
+
return pace;
|
|
24893
|
+
}
|
|
24721
24894
|
/** A group's pin now (WISP 400 § Pinned message), the latest one taken, unpinned or not. */
|
|
24722
24895
|
pinOf(groupId) {
|
|
24723
24896
|
return this.record(groupId)?.pin;
|
|
@@ -25030,6 +25203,15 @@ var Groups = class {
|
|
|
25030
25203
|
if (!groupOfFile(fileId, [...this.stored.keys(), ...this.communities.ids()]) || !this.files) throw new Error("No such file");
|
|
25031
25204
|
return this.files.download(fileId);
|
|
25032
25205
|
}
|
|
25206
|
+
/** The person stops a group's file that is coming or would be fetched (Cancel, Decline), or pauses it (`keep`). */
|
|
25207
|
+
haltFile(fileId, keep = false) {
|
|
25208
|
+
if (!groupOfFile(fileId, [...this.stored.keys(), ...this.communities.ids()]) || !this.files) throw new Error("No such file");
|
|
25209
|
+
return this.files.halt(fileId, keep);
|
|
25210
|
+
}
|
|
25211
|
+
/** Automatic downloads were turned off: the groups' files chosen by themselves and not here yet stop coming. */
|
|
25212
|
+
autoDownloadsOff() {
|
|
25213
|
+
return this.files?.autoDownloadsOff() ?? Promise.resolve();
|
|
25214
|
+
}
|
|
25033
25215
|
/** An edge's files/3 session opened or closed (WISP 503: the bytes of a group's files go over it). */
|
|
25034
25216
|
fileSession(groupId, peerKey, linkId, open) {
|
|
25035
25217
|
this.files?.session(groupId, peerKey, linkId, open);
|
|
@@ -25328,6 +25510,7 @@ var Groups = class {
|
|
|
25328
25510
|
this.gapsAsked.delete(groupId);
|
|
25329
25511
|
this.gapsLooked.delete(groupId);
|
|
25330
25512
|
this.typings.forget(groupId);
|
|
25513
|
+
for (const key of this.pinPaces.keys()) if (key.startsWith(`${groupId}\n`)) this.pinPaces.delete(key);
|
|
25331
25514
|
this.downSince.delete(groupId);
|
|
25332
25515
|
this.hereHeard.delete(groupId);
|
|
25333
25516
|
this.hereActed.delete(groupId);
|
|
@@ -25956,6 +26139,8 @@ var Groups = class {
|
|
|
25956
26139
|
return;
|
|
25957
26140
|
}
|
|
25958
26141
|
if (t === "group-pin") {
|
|
26142
|
+
const said = this.pinPace(groupId, peerKey).take() ? readPin(frame) : null;
|
|
26143
|
+
if (!said || !pinIsNewer(this.record(groupId)?.pin, said)) return;
|
|
25959
26144
|
const signed = session.signedPin(frame);
|
|
25960
26145
|
if (signed && rosterHas(session.roster, peerKey)) await this.host.groupPin?.(groupId, peerKey, signed);
|
|
25961
26146
|
return;
|
|
@@ -28068,7 +28253,7 @@ var HoldEngine = class {
|
|
|
28068
28253
|
this.host.changed();
|
|
28069
28254
|
return;
|
|
28070
28255
|
}
|
|
28071
|
-
const pointer = packet ? keys.readPointer(packet) : null;
|
|
28256
|
+
const pointer = packet ? keys.readPointer(packet, this.host.loopbackStorage?.()) : null;
|
|
28072
28257
|
if (!pointer) return;
|
|
28073
28258
|
let hold = this.state(linkId);
|
|
28074
28259
|
if (pointer.rev < hold.peerPointerRev) return;
|
|
@@ -28107,11 +28292,12 @@ var HoldEngine = class {
|
|
|
28107
28292
|
try {
|
|
28108
28293
|
const manifest = keys.open(await this.fetchBytes(pointer.manifestUrl, HOLD_LIMITS.maxManifestBytes), {
|
|
28109
28294
|
maxBytes: HOLD_LIMITS.maxManifestBytes,
|
|
28110
|
-
now
|
|
28295
|
+
now,
|
|
28296
|
+
loopback: this.host.loopbackStorage?.()
|
|
28111
28297
|
});
|
|
28112
28298
|
if (manifest.header.kind !== "manifest") throw new HoldRefusedError("format", "Not a manifest");
|
|
28113
28299
|
mailbox = manifest.header.mailbox;
|
|
28114
|
-
entries = readManifest(manifest.header.meta).filter(([seq]) => seq > this.state(linkId).inSeq).sort((a, b) => a[0] - b[0]);
|
|
28300
|
+
entries = readManifest(manifest.header.meta, this.host.loopbackStorage?.()).filter(([seq]) => seq > this.state(linkId).inSeq).sort((a, b) => a[0] - b[0]);
|
|
28115
28301
|
} catch (error) {
|
|
28116
28302
|
if (error instanceof HoldRefusedError) {
|
|
28117
28303
|
await this.save(linkId, {
|
|
@@ -28253,9 +28439,11 @@ var HoldEngine = class {
|
|
|
28253
28439
|
}
|
|
28254
28440
|
}
|
|
28255
28441
|
async readBytes(url, max, signal) {
|
|
28442
|
+
if (!isHoldUrl(url, this.host.loopbackStorage?.())) throw new Error("The storage address is not one this app reads");
|
|
28256
28443
|
const response = await (this.host.fetch ?? ((...args) => fetch(...args)))(url, {
|
|
28257
28444
|
cache: "no-store",
|
|
28258
28445
|
credentials: "omit",
|
|
28446
|
+
redirect: "error",
|
|
28259
28447
|
signal
|
|
28260
28448
|
});
|
|
28261
28449
|
if (!response.ok) throw new Error(`The storage answered ${response.status}`);
|
|
@@ -34460,12 +34648,12 @@ var GhostlyNode = class GhostlyNode {
|
|
|
34460
34648
|
await this.messagesChanged(linkId, [messageId]);
|
|
34461
34649
|
this.emitState();
|
|
34462
34650
|
},
|
|
34463
|
-
text: async (linkId, messageId) => (await db.
|
|
34651
|
+
text: async (linkId, messageId) => (await db.getMessage(linkId, messageId))?.text ?? null,
|
|
34464
34652
|
reply: async (linkId, messageId) => {
|
|
34465
|
-
const message =
|
|
34653
|
+
const message = await db.getMessage(linkId, messageId);
|
|
34466
34654
|
return message && GhostlyNode.wireReply(message);
|
|
34467
34655
|
},
|
|
34468
|
-
forwarded: async (linkId, messageId) => (await db.
|
|
34656
|
+
forwarded: async (linkId, messageId) => (await db.getMessage(linkId, messageId))?.forwarded,
|
|
34469
34657
|
file: async (fileId) => {
|
|
34470
34658
|
const stored = await fileStore.get(fileId);
|
|
34471
34659
|
if (!stored?.metadata) return null;
|
|
@@ -34569,6 +34757,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
34569
34757
|
},
|
|
34570
34758
|
receivePaymentRequest: (linkId, request) => this.desk.onPaymentRequest(linkId, request, true),
|
|
34571
34759
|
changed: () => this.emitState(),
|
|
34760
|
+
loopbackStorage: () => this.options.holdLoopback === true,
|
|
34572
34761
|
ttlMs: () => {
|
|
34573
34762
|
try {
|
|
34574
34763
|
const raw = typeof localStorage !== "undefined" ? localStorage.getItem("ghostly-test-hold-ttl") : null;
|
|
@@ -35805,8 +35994,11 @@ var GhostlyNode = class GhostlyNode {
|
|
|
35805
35994
|
appCheckUpdates() {
|
|
35806
35995
|
return this.appStore.checkUpdates();
|
|
35807
35996
|
}
|
|
35808
|
-
|
|
35809
|
-
|
|
35997
|
+
/** Uninstalls an app. One running then is closed in every chat (the contact hears `close`), and the pages are told, to stop its frames. */
|
|
35998
|
+
async appUninstall(params) {
|
|
35999
|
+
await this.appStore.uninstall(params);
|
|
36000
|
+
this.appsCloseOf(params.ref);
|
|
36001
|
+
this.events.onAppsChecked?.();
|
|
35810
36002
|
}
|
|
35811
36003
|
appRunCheck(params) {
|
|
35812
36004
|
return this.appStore.runCheck(params);
|
|
@@ -37206,7 +37398,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37206
37398
|
async retryMessage({ linkId, messageId }) {
|
|
37207
37399
|
const live = this.links.get(linkId);
|
|
37208
37400
|
if (!live?.stored.profile) throw new Error("Retry is only available for paired chat messages");
|
|
37209
|
-
const message =
|
|
37401
|
+
const message = await db.getMessage(linkId, messageId);
|
|
37210
37402
|
if (message?.via === "hold") {
|
|
37211
37403
|
if (await this.hold.retry(linkId, messageId)) return;
|
|
37212
37404
|
if (!this.holdingFor(live)) throw new Error("Held messages need S3 storage (Profile → Backups) and a contact that allows them.");
|
|
@@ -37326,7 +37518,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37326
37518
|
*/
|
|
37327
37519
|
async messageDetails({ linkId, messageId }) {
|
|
37328
37520
|
if (typeof linkId !== "string" || typeof messageId !== "string") throw new Error("Chat not found");
|
|
37329
|
-
const message =
|
|
37521
|
+
const message = await db.getMessage(linkId, messageId);
|
|
37330
37522
|
if (!message) return null;
|
|
37331
37523
|
const live = this.links.get(linkId), stored = live?.stored;
|
|
37332
37524
|
const group = linkId.startsWith("group:") ? (await db.getGroups()).find((g) => g.id === linkId.slice(6)) : void 0;
|
|
@@ -37533,12 +37725,22 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37533
37725
|
}
|
|
37534
37726
|
}
|
|
37535
37727
|
/**
|
|
37728
|
+
* The message of mine that carries a payment or a request, read by its id: `me_<the time the desk keeps for it>`.
|
|
37729
|
+
* The chat's history is read only when that row does not carry it, so it costs the same in a long chat as in a short one.
|
|
37730
|
+
*/
|
|
37731
|
+
async paymentMessage(linkId, paymentId) {
|
|
37732
|
+
const at = this.desk.payment(paymentId)?.createdAt;
|
|
37733
|
+
const message = at === void 0 ? void 0 : await db.getMessage(linkId, `me_${at}`);
|
|
37734
|
+
if (message?.sender === "me" && message.paymentId === paymentId) return message;
|
|
37735
|
+
return (await db.getMessages(linkId)).find((m) => m.sender === "me" && m.paymentId === paymentId);
|
|
37736
|
+
}
|
|
37737
|
+
/**
|
|
37536
37738
|
* The contact's app answered for a payment of mine (`pay-res`): its message gets two ticks, as a text gets from its
|
|
37537
37739
|
* receipt. A payment goes with no delivery state of its own, so its mark read one tick for good. 1:1 chats only: a
|
|
37538
37740
|
* group's payments live in the group's history, under other ids.
|
|
37539
37741
|
*/
|
|
37540
37742
|
async notePaymentReceipt(linkId, paymentId) {
|
|
37541
|
-
const message =
|
|
37743
|
+
const message = await this.paymentMessage(linkId, paymentId);
|
|
37542
37744
|
if (!message || message.delivery === "delivered") return;
|
|
37543
37745
|
if (await db.patchMessage(linkId, message.id, (m) => m.delivery === "delivered" ? null : {
|
|
37544
37746
|
delivery: "delivered",
|
|
@@ -37756,9 +37958,10 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37756
37958
|
}
|
|
37757
37959
|
fileAction({ linkId, fileId, action }) {
|
|
37758
37960
|
if (typeof linkId === "string" && linkId.startsWith("group:")) {
|
|
37759
|
-
if (action !== "accept" && action !== "request") throw new Error("A group's file is downloaded (accept) or asked for again (request)");
|
|
37760
37961
|
if (!fileId.startsWith(`group-${linkId.slice(6)}-`)) throw new Error("No such file in this group");
|
|
37761
|
-
return this.groups.downloadFile(fileId);
|
|
37962
|
+
if (action === "accept" || action === "request" || action === "resume") return this.groups.downloadFile(fileId);
|
|
37963
|
+
if (action === "cancel" || action === "decline" || action === "pause") return this.groups.haltFile(fileId, action === "pause");
|
|
37964
|
+
throw new Error("A group's file is not sent again from here");
|
|
37762
37965
|
}
|
|
37763
37966
|
const live = this.links.get(linkId);
|
|
37764
37967
|
if (!live) throw new Error("No such chat");
|
|
@@ -37847,7 +38050,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37847
38050
|
const error = await link.announceFile(offer, message.timestamp, GhostlyNode.wireReply(message), message.forwarded);
|
|
37848
38051
|
const said = !error && link.dhtPendingId === dhtFileId(offer.wireId);
|
|
37849
38052
|
if (!said && error !== "This file's name is too long to say over the DHT. It is sent when you are live.") return;
|
|
37850
|
-
await db.patchMessage(linkId, message.id, (current) => current.delivery !== "waiting" || current.fileOnFloor ? null : {
|
|
38053
|
+
const noted = await db.patchMessage(linkId, message.id, (current) => current.delivery !== "waiting" || current.fileOnFloor ? null : {
|
|
37851
38054
|
fileOnFloor: said ? "sent" : "unfit",
|
|
37852
38055
|
...said && { details: withSend(current.details, {
|
|
37853
38056
|
at,
|
|
@@ -37855,17 +38058,32 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37855
38058
|
result: "sent"
|
|
37856
38059
|
}) }
|
|
37857
38060
|
});
|
|
38061
|
+
if (said && noted) await live.floorFiles?.then((known) => known.set(dhtFileId(offer.wireId), message.id), () => {});
|
|
37858
38062
|
await this.messagesChanged(linkId, [message.id]);
|
|
37859
38063
|
}
|
|
37860
38064
|
/** A receipt on the DHT floor that names a waiting file's offer (`dhtFileId`): its bubble is in place on the contact's side. */
|
|
37861
38065
|
async fileSeenOnFloor(linkId, id) {
|
|
37862
|
-
const
|
|
37863
|
-
|
|
37864
|
-
|
|
37865
|
-
if (
|
|
38066
|
+
const live = this.links.get(linkId);
|
|
38067
|
+
if (!live) return;
|
|
38068
|
+
const said = await this.floorFilesOf(live), messageId = said.get(id);
|
|
38069
|
+
if (!messageId) return;
|
|
38070
|
+
said.delete(id);
|
|
38071
|
+
if (await db.patchMessage(linkId, messageId, (current) => current.fileOnFloor !== "sent" ? null : {
|
|
37866
38072
|
fileOnFloor: "seen",
|
|
37867
38073
|
...current.delivery === "waiting" && { deliveryError: "Your contact sees it. The file goes when you are live." }
|
|
37868
|
-
})) await this.messagesChanged(linkId, [
|
|
38074
|
+
})) await this.messagesChanged(linkId, [messageId]);
|
|
38075
|
+
}
|
|
38076
|
+
/**
|
|
38077
|
+
* The files of mine said on the floor and not seen there yet, their message by their floor id (`dhtFileId`). Read from
|
|
38078
|
+
* the chat's history once in a run, then kept as files are said (`sayFileOnFloor`): a receipt that names none of them
|
|
38079
|
+
* costs the same in a long chat as in a short one.
|
|
38080
|
+
*/
|
|
38081
|
+
floorFilesOf(live) {
|
|
38082
|
+
const linkId = live.stored.id, prefix = `${linkId}-out-`;
|
|
38083
|
+
return live.floorFiles ??= db.getMessages(linkId).then((history) => new Map(history.filter((m) => m.sender === "me" && m.file?.id.startsWith(prefix) && m.fileOnFloor === "sent").map((m) => [dhtFileId(m.file.id.slice(prefix.length)), m.id])), (error) => {
|
|
38084
|
+
live.floorFiles = void 0;
|
|
38085
|
+
throw error;
|
|
38086
|
+
});
|
|
37869
38087
|
}
|
|
37870
38088
|
/**
|
|
37871
38089
|
* A file the contact said on the DHT floor (WISP 403 § Files): its bubble takes its place now, waiting for its bytes,
|
|
@@ -38865,10 +39083,13 @@ var GhostlyNode = class GhostlyNode {
|
|
|
38865
39083
|
* This side opened an app in a paired chat, or updated it: `open` goes to the contact now when the session is live,
|
|
38866
39084
|
* and again on every session that comes back while it stays open.
|
|
38867
39085
|
*/
|
|
38868
|
-
async appOpen({ linkId, ref, version }) {
|
|
39086
|
+
async appOpen({ linkId, ref, version, digest }) {
|
|
38869
39087
|
const { live, app } = this.appChat(linkId, ref);
|
|
38870
39088
|
if (!isAppVersion(version)) throw new Error("Not an app version");
|
|
38871
|
-
await this.appStore.chatRunnable({
|
|
39089
|
+
await this.appStore.chatRunnable({
|
|
39090
|
+
ref,
|
|
39091
|
+
digest
|
|
39092
|
+
});
|
|
38872
39093
|
const open = live.appsOpen ??= /* @__PURE__ */ new Map();
|
|
38873
39094
|
if (live.link) live.link.openApp(app, version);
|
|
38874
39095
|
else open.set(app, version);
|
|
@@ -38881,10 +39102,13 @@ var GhostlyNode = class GhostlyNode {
|
|
|
38881
39102
|
else live.appsOpen?.delete(app);
|
|
38882
39103
|
}
|
|
38883
39104
|
/** One data frame of an app open on both sides, on the live session only; nothing is kept to send later. */
|
|
38884
|
-
async appSend({ linkId, ref, data }) {
|
|
39105
|
+
async appSend({ linkId, ref, data, digest }) {
|
|
38885
39106
|
const { live, app } = this.appChat(linkId, ref);
|
|
38886
39107
|
if (!live.appsOpen?.has(app)) return { error: "not-open" };
|
|
38887
|
-
await this.appStore.chatRunnable({
|
|
39108
|
+
await this.appStore.chatRunnable({
|
|
39109
|
+
ref,
|
|
39110
|
+
digest
|
|
39111
|
+
});
|
|
38888
39112
|
return { error: live.link ? live.link.sendAppData(app, data) : "offline" };
|
|
38889
39113
|
}
|
|
38890
39114
|
/** Every page went away: no app runs here any more, so each open one is closed (`close` goes where it can). */
|
|
@@ -38892,6 +39116,16 @@ var GhostlyNode = class GhostlyNode {
|
|
|
38892
39116
|
for (const live of this.links.values()) for (const app of [...live.appsOpen?.keys() ?? []]) if (live.link) live.link.closeApp(app);
|
|
38893
39117
|
else live.appsOpen?.delete(app);
|
|
38894
39118
|
}
|
|
39119
|
+
/** An app that went (uninstalled) is closed in every chat it was open in, as `appClose` does. */
|
|
39120
|
+
appsCloseOf(ref) {
|
|
39121
|
+
for (const [linkId, live] of this.links) {
|
|
39122
|
+
if (!live.appsOpen?.size) continue;
|
|
39123
|
+
const { app } = this.appChat(linkId, ref);
|
|
39124
|
+
if (!live.appsOpen.has(app)) continue;
|
|
39125
|
+
if (live.link) live.link.closeApp(app);
|
|
39126
|
+
else live.appsOpen.delete(app);
|
|
39127
|
+
}
|
|
39128
|
+
}
|
|
38895
39129
|
/** Keeps a pin of a 1:1 chat when it is newer than the chat's; `out`: mine, to say until the contact confirms it. */
|
|
38896
39130
|
async keepLinkPin(linkId, pin, out) {
|
|
38897
39131
|
const live = this.links.get(linkId);
|
|
@@ -38954,6 +39188,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
38954
39188
|
const view = this.groups.views().find((g) => g.id === groupId);
|
|
38955
39189
|
if (!pin || !view?.myKey || view.status !== "active" || !view.members.some((m) => m.key === member)) return;
|
|
38956
39190
|
if (!mayPin(view.profile, member, view.members.find((m) => m.role === "admin")?.key)) return;
|
|
39191
|
+
if (!pinIsNewer(this.groups.pinOf(groupId), pin)) return;
|
|
38957
39192
|
const kept = await this.pinCame(`group:${groupId}`, member === view.myKey ? "me" : member, pin);
|
|
38958
39193
|
if (!await this.groups.setPin(groupId, {
|
|
38959
39194
|
...kept,
|
|
@@ -39241,7 +39476,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
39241
39476
|
async closeRequest(paymentId, reason) {
|
|
39242
39477
|
const request = this.desk.payment(paymentId);
|
|
39243
39478
|
if (!request || !await this.desk.close(paymentId, reason, "your contact removed the wallet it was paid to")) return;
|
|
39244
|
-
const message =
|
|
39479
|
+
const message = await this.paymentMessage(request.linkId, paymentId);
|
|
39245
39480
|
if (message) await this.hold.forget(request.linkId, message.id).catch(() => {});
|
|
39246
39481
|
}
|
|
39247
39482
|
/**
|
|
@@ -40080,6 +40315,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
40080
40315
|
if (settings[key] !== false) delete this.settings[key];
|
|
40081
40316
|
else this.settings[key] = false;
|
|
40082
40317
|
await db.putSettings(this.settings);
|
|
40318
|
+
if (key === "autoDownloads" && settings[key] === false) await this.groups.autoDownloadsOff();
|
|
40083
40319
|
}
|
|
40084
40320
|
if (settings.publicProfiles !== void 0) {
|
|
40085
40321
|
if (settings.publicProfiles !== false) delete this.settings.publicProfiles;
|
|
@@ -41627,10 +41863,11 @@ var GhostlyNode = class GhostlyNode {
|
|
|
41627
41863
|
this.notePaymentReceipt(linkId, result.id).catch(() => {});
|
|
41628
41864
|
},
|
|
41629
41865
|
onFileStored: async (wire) => {
|
|
41630
|
-
|
|
41631
|
-
|
|
41632
|
-
if (
|
|
41633
|
-
return
|
|
41866
|
+
if (!stored.group && !this.links.get(linkId)?.files.wireIds.has(wire.id)) return void 0;
|
|
41867
|
+
const held = (await fileStore.listForLink(linkId)).find((file) => file.direction === "in" && file.wireId === wire.id);
|
|
41868
|
+
if (!held?.digest || !held.metadata) return void 0;
|
|
41869
|
+
if (held.metadata.name !== wire.name || held.metadata.size !== wire.size || held.metadata.mime !== wire.mime || held.metadata.timestamp !== wire.timestamp) return void 0;
|
|
41870
|
+
return held.digest;
|
|
41634
41871
|
},
|
|
41635
41872
|
onFileIncoming: (file) => this.receiveFile(linkId, file),
|
|
41636
41873
|
onFileProgress: (id, transferred, direction) => this.fileProgress(this.localFileId(linkId, id, direction), transferred),
|
|
@@ -41769,7 +42006,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
41769
42006
|
get nativeFactories() {
|
|
41770
42007
|
const relay = this.relaysHyperdht ? this.hyperdhtRelay : "";
|
|
41771
42008
|
return {
|
|
41772
|
-
...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-
|
|
42009
|
+
...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-CgYLaQz3.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
|
|
41773
42010
|
...this.options.nativeTransports,
|
|
41774
42011
|
...this.ownIrohRelays ? { "iroh/1": (seedB64) => this.options.nativeTransports["iroh/1"](seedB64, this.settings.irohRelays?.length ? { relays: [...this.settings.irohRelays] } : {}) } : {},
|
|
41775
42012
|
...this.options.irohWeb ? { "iroh/1": (seedB64) => createIrohWebEndpoint(seedB64, { relays: this.irohRelays }) } : {}
|
|
@@ -42574,4 +42811,4 @@ function signalKind(signal) {
|
|
|
42574
42811
|
//#endregion
|
|
42575
42812
|
export { EngineServer, hyperdhtRelayProblem as n, server_exports as t };
|
|
42576
42813
|
|
|
42577
|
-
//# sourceMappingURL=server-
|
|
42814
|
+
//# sourceMappingURL=server-Cy4ox7oD.js.map
|