@ghostlytools/cli 1.1.5 → 1.1.6
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 +1 -0
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/{endpoint-CaSbSFDw.js → endpoint-D_3bYafG.js} +76 -3
- package/dist/assets/endpoint-D_3bYafG.js.map +1 -0
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
- package/dist/assets/{hyperdhtRelay-w9cwVqGZ.js → hyperdhtRelay-CnEkFl8R.js} +3 -3
- package/dist/assets/{hyperdhtRelay-w9cwVqGZ.js.map → hyperdhtRelay-CnEkFl8R.js.map} +1 -1
- package/dist/assets/idb-BIdus0WX.js.map +1 -1
- package/dist/assets/{mainline-BabMhm8L.js → mainline-DQxDSWXm.js} +2 -2
- package/dist/assets/{mainline-BabMhm8L.js.map → mainline-DQxDSWXm.js.map} +1 -1
- package/dist/assets/{server-TamKsfmC.js → server-HMzdsyn_.js} +324 -53
- package/dist/assets/server-HMzdsyn_.js.map +1 -0
- package/dist/assets/{src-C6qQYrAH.js → src-DtxVo2Rm.js} +589 -69
- package/dist/assets/src-DtxVo2Rm.js.map +1 -0
- package/dist/ghostly.mjs +1068 -49
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/assets/endpoint-CaSbSFDw.js.map +0 -1
- package/dist/assets/server-TamKsfmC.js.map +0 -1
- package/dist/assets/src-C6qQYrAH.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import { $
|
|
2
|
+
import { $i as formatFileSize, $n as GROUP_FILE_LIMITS, $o as handoffEphemeral, $r as WAKE_CALL_INTERVAL_MS, $s as deviceKeyZ32, $t as SEPOLIA_TEST_USDT_FAUCET, A as entryParams, Aa as replySnippet, Ac as createRelayPayload, Ai as IdentityExchange, Ao as LEGACY_SERVICES, Ar as newerHead, As as enrollCancelReason, Ba as claimedTime, Bc as sanitizeAvatar, Bi as emptyProofLedger, Bn as engineText, Bo as HANDOFF_REQUEST, Br as transferStalled, Bs as deviceInviteJoinerParams, C as GROUP_READ_NOTE_COMMUNITY, Ca as reactionEmoji, Ci as DHT_TEXT_CAPABILITY, Cr as beaconRecords, Cs as signTombstone, Da as wireReaction, Dc as withRequestOptions, Dr as lobbyRecords, E as encodeCommunityLink, Ea as validReactionNumber, Ec as isDiscoveryBudgetError, Eo as parseCallSignal, Er as lobbyKeys, Fc as identityFromSeed, Fi as newIdentityBinding, Fo as HANDOFF_HAVE, Fr as readLobby, Fs as enrollProofFrame, G as GROUP_EDIT_TEXT_BYTES, Gc as toZ32, Gn as findEndpoint, Go as handoffAttemptFailed, Gr as typingActivity, Gs as encodeInviteCode, Gt as encodeDidDhtPacket, H as GroupSession, Ha as receivedTimestamp, Hc as fromBase64Url, Hi as TRANSPORTS, Hn as ENDPOINT, Ho as HANDOFF_VERIFIED, Hs as OwnInviteError, Ht as didDhtDocument, I as GroupWakeLimiter, Ia as checkStatusCard, Ic as identityFromSeedB64, Ii as receivedIdentityStatus, Io as HANDOFF_HELLO, Ir as shouldBeHub, Is as readEnrollGrant, J as mentionsMember, Ji as peekDhtMailbox, Jn as parseFedimintRequestPayload, Jo as handoffCancelFrame, Jr as mayPin, Js as DEVICES_CAPABILITY, Jt as ETHEREUM_USDT, K as carryMentions, Ka as appFingerprint, Kc as utf8Decode, Kn as isFederationId, Ko as handoffAttemptSucceeded, L as groupWakeFrame, La as sameButtonText, Lc as publicKeyFromZ32, Li as revokesIdentity, Lo as HANDOFF_OFFER, Lr as ChatFiles, Ls as readEnrollHello, M as knockRecords, Mi as emptyIdentityLedger, Mo as HANDOFF_ACK, Mr as rankHubs, Ms as enrollDoneFrame, N as mergeKnocks, Ni as identityRevocationValue, No as HANDOFF_CANCEL, Nr as readBeacon, Ns as enrollGrantFrame, O as decodeGroupEntryLink, Oa as pairedReplyAuthor, Oc as PacketTooLargeError, Oi as IDENTITY_REVOCATION_LABEL, Oo as emptyLinkRecords, Or as mergeBeacon, P as readKnocks, Pa as STATUS_CARD_LIMITS, Pc as createIdentity, Pi as identityStatement, Po as HANDOFF_DONE, Pr as readBeaconHead, Ps as enrollHelloFrame, Qn as pairedMessageFrame, Qo as handoffDigest, Qr as readPin, Qs as deviceEchoNonce, Qt as SEPOLIA_TEST_USDT, R as groupWakes, Ra as statusCardText, Rc as verify, Ri as lastSharedWithMe, Ro as HANDOFF_PAKE, Rr as FILE_LIMITS, Rs as verifyEnrollProof, S as CommunitySession, Sa as queueReaction, Sc as normalizeRelayUrl, Si as CapsExchange, Sr as beaconKeys, Ss as setUpdateFrame, T as decodeCommunityLink, Ta as readReaction, Tc as budgetRetryMs, Ti as dialDescriptors, Tr as freshHubs, Ua as shownTime, Uc as randomBytes, Ui as automaticTransport, Un as cashuRequestPayload, Uo as filePartName, Ur as TypingSender, Us as createChatInvite, Ut as didDhtFromPublicKey, V as GROUP_READ_NOTE, Va as heardTime, Vc as bytesEqual, Vi as validExternalKey, Vn as decodeBolt11, Vo as HANDOFF_TIMINGS, Vs as readDeviceInvite, Wc as toBase64Url, Wn as fedimintRequestPayload, Wo as handoffAttemptAllowed, Ws as decodeInviteCode, Wt as encodeDidDhtDeactivated, Xa as isAppRef, Xi as dhtFileId, Xn as INVITE_TAKEN, Xo as handoffContextProof, Xr as pinIsNewer, Xs as HANDOFF_CAPABILITY, Xt as ONCHAIN_PROVIDER, Y as groupName, Ya as isAppKey, Yi as DHT_FILE_CAPABILITY, Yn as GhostLink, Yo as handoffContext, Yr as nextPinNumber, Ys as ENROLL_CAPABILITY, Yt as EVM_TEST_CHAINS, Za as readAppRevocations, Zi as parseImageMeta, Zn as LIVENESS_PING_MS, Zo as handoffDataFrame, Zr as pinNumberHolds, Zs as deviceEchoFrame, Zt as PaymentPreflightError, _ as GroupTypingBoard, _a as COMMUNITY_REACTION_FRAME, _c as turnKeys, _i as HOLD_LIMITS, _o as decodeControl, _r as MESH_HUBS, _s as sealHandoffPiece, ai as wakeRequest, an as formatPaymentAmount, ao as rosterHas, as as handoffRetryAfter, b as sealGroupTyping, ba as ReactionWindow, bc as RelayTransport, bi as newMailbox, bo as RTC_CONFIG, br as pickMeshHubs, bs as readSetUpdate, c as APP_BUNDLE_LIMITS, cc as classifyTurnRead, ci as chatAppId, cn as SPARK_NETWORKS, co as DirectPathWatch, cs as newHandoffId, d as appViewOf, da as EDIT_RATE_WINDOW_MS, dc as TURN_MAX, di as BUTTONS_CAPABILITY, dn as sparkInvoiceDetails, do as presenceSeenAt, ds as readHandoffDone, ea as safeBlobType, ec as deviceLinkPairing, ei as WakeLimiter, en as TEST_USDT_FAUCET_AMOUNT, eo as canonicalJson, es as handoffHelloFrame, fa as RateWindow, fc as firstDeviceSetSecret, fi as EDIT_CAPABILITY, fn as isBitcoinAddress, fo as GhostlyHttpError, fs as readHandoffHave, ga as readForwarded, gc as signTurnRelease, gi as HOLD_DATED_BACK_MS, go as LIMITS, gr as readGroupSignal, gs as readHandoffVerified, gt as normalizeAtprotoHandle, h as readAppBundle, ha as forwardedAgain, hc as signTurnPacket, hi as HOLD_CAPABILITY, ho as webLocalFetch, hr as groupSignalFrame, hs as readHandoffTurnFrame, ht as isAtprotoDid, i as planAppUpdate, ic as webCryptoSigner, ii as relayRequest, in as assertWholeSats, io as MEMBER_KEY, ir as groupFileFallback, is as handoffReleaseFrame, j as knockIdentity, ja as BUTTON_ID, jo as serviceIdFromName, jr as pickHubs, js as enrollDigits, k as encodeGroupEntryLink, ka as readReply, ko as HTTP_SERVICE_PROTO, kr as mergeLobby, ks as enrollCancelFrame, l as APP_BUNDLE_MAGIC, la as parseVideoMeta, lc as turnPutSummary, li as isAppVersion, ln as SPARK_PROVIDER, ls as readHandoffAck, m as isAppUrl, ma as parseLinkPreview, mc as readTurnPacket, mi as UPGRADE_CAPABILITY, mn as isPubkyKey, mo as parseLocalTarget, ms as readHandoffPake, n as appUpdateDecision, nc as newDeviceSetSecret, ni as newWakeToken, nn as WALLET_NETWORKS, no as GROUP_MEMBER_CAP, ns as handoffOfferFrame, o as readAppStore, oa as fileMessageText, oc as TURN_RAISE_READ_TIMEOUT_MS, oi as checkPushEndpoint, on as validatePaymentTarget, oo as verifyCommitSignature, or as readGroupFileMeta, os as handoffStreamKey, pa as dhtEditId, pc as nextTurnPosition, pi as STATUS_CARD_CAPABILITY, po as formatLocalTarget, ps as readHandoffHello, q as parseCommunityEdit, qa as appRef, qc as utf8Encode, qi as DHT_MESSAGE_TTL, qn as parseCashuRequestPayload, qo as handoffBusyFrame, qr as PIN_LIMITS, qs as inviteOwnership, qt as signDidDhtPacket, r as checkAppBeforeRun, rc as seedSigner, ri as parseWakeFrame, rn as assertTokenUnits, rs as handoffPakeFrame, sa as parseVoiceMeta, sc as TURN_SETTLE_MS, si as vapidKeysMatch, sn as walletNetworkOf, so as edgeParams, ss as handoffVersions, t as appStoreDecision, tc as devicePingFrame, ti as deviceWakeRequest, tn as USDT_PUBLIC_RPC, ts as handoffManifestFrames, u as appDigest, ua as EDIT_BUFFER_MS, uc as TURN_LAST_SEQUENCE, ui as APPS_CAPABILITY, un as sparkAddressKind, uo as RELAY_POLL_INTERVALS, ur as CarriedTransport, us as readHandoffCancel, v as groupTypingEpoch, va as GROUP_REACTION_FRAME, vc as turnName, vi as HoldKeys, vo as encodeControl, vr as mayBeHub, w as communityHasFrame, wa as reactionIsNewer, wc as traceLink, wi as capsDescriptors, wr as doorHubs, ws as verifySetUpdate, x as COMMUNITY_LIMITS, xa as nextReactionNumber, xc as currentRelays, xi as readManifest, xr as COMMUNITY_TOPOLOGY, xs as setAckFrame, y as openGroupTyping, ya as REACTION_LIMITS, yc as DEFAULT_RELAYS, yi as HoldRefusedError, yr as meshHubs, z as parseGroupWakeFrame, za as arrivalKey, zi as PeerProofs, zn as engineError, zo as HANDOFF_PIECE_BYTES, zr as transferEnded, zs as createDeviceInvite } from "./src-DtxVo2Rm.js";
|
|
3
3
|
import { a as clearProfileStores, c as fileStore, d as store, f as transact, i as STORES$1, l as openDb, n as APP_STORAGE_SCOPE_INDEX, o as databaseName, p as wrap, r as CARD_INDEX, s as deleteDatabase, t as APP_STORAGE_CHAT_INDEX, u as profileOpenFailure } from "./idb-BIdus0WX.js";
|
|
4
4
|
import { a as fileBytesOf, c as removeFileBytes, i as fileBytes, l as FILE_BYTES_STEP, n as blobDigest } from "./fileBytes-3AQ2tJzW.js";
|
|
5
|
-
import { $ as BACKUP_MEDIA_TYPE, A as requireTurn, At as
|
|
5
|
+
import { $ as BACKUP_MEDIA_TYPE, A as requireTurn, At as paymentWireId, B as networkLabel, C as breezDatabasesFor, Ct as storedGroupFiles, D as sourceKey, Dt as replyRef, E as ProviderSources, Et as receivedPairedReply, F as sealSeed, Ft as streamStored, G as BITCOIN_INVOICE_ON_TESTNET, H as cashuMint, I as unsealSeed, J as fakesLightning, K as DEFAULT_MINTS, L as ModeChanged, M as setSingleWriterGate, Mt as readStored$1, N as intentRepository, Nt as removeStored, O as awayFrom, Ot as replyTo, P as newDeviceKey, Pt as storedSize, Q as paysItsOwnInvoices, R as ModeGate, S as breezDatabaseInUse, St as groupOfFile, T as lightningPaying, Tt as pairedWireReply, V as CASHU_MINT_SOURCE, W as WALLET_TYPES, X as isWorthlessMint, Y as isTestMint, Z as mintNetwork, _ as LightningCards, _t as verifyIdentity, a as irohRelayProblem, at as reserveAdapterIds, b as normalizePhrase, bt as groupFileId, c as createFailure, ct as SourceConfigError, d as paymentNetwork, dt as networkMode, et as MAX_BACKUP_BYTES, f as paymentNetworksOf, ft as redact, g as CASHU_CARD, gt as boundedIdentityFetch, h as rpcHost, ht as setOwnDidSource, i as createIrohWebEndpoint, it as registeredOnchainProviders, j as setAwayWallets, jt as FileAppender, k as refuseAway, kt as paymentAlias, l as createTiming, lt as SourceUnreachableError, m as networkReason, mt as resolveAtprotoDid, n as loadFedimintSdk, nt as onAdaptersChanged, o as WALLET_NAMES, ot as NothingSpentError, p as walletInstances, pt as assertPublicServer, q as TEST_MINT, r as DEFAULT_IROH_RELAYS, rt as registeredLightningProviders, s as assertConfirmedReal, st as PROVIDER_PLATFORMS, t as fedimintDatabase, tt as identityProviders, u as crossNetwork, ut as isNothingSpentError, v as cardsKey, vt as chosenResolver, w as dropBreezDatabase, wt as groupReply, x as breezDatabase, xt as groupFileText, y as isRecoveryPhrase, yt as GroupFileDesk, z as WrongNetworkError } from "../ghostly.mjs";
|
|
6
6
|
import { n as traceJoin } from "./joinTrace-CO8mlhNO.js";
|
|
7
7
|
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
8
8
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
@@ -12685,6 +12685,7 @@ var Apps = class {
|
|
|
12685
12685
|
version: app.manifest.version,
|
|
12686
12686
|
title: app.manifest.title,
|
|
12687
12687
|
permissions: [...app.permissions],
|
|
12688
|
+
view: appViewOf(bundle.manifest),
|
|
12688
12689
|
entry: utf8Decode(entry)
|
|
12689
12690
|
};
|
|
12690
12691
|
}
|
|
@@ -12710,6 +12711,7 @@ var Apps = class {
|
|
|
12710
12711
|
sequence: app.sequence,
|
|
12711
12712
|
digest: app.digest,
|
|
12712
12713
|
permissions: [...app.permissions],
|
|
12714
|
+
view: appViewOf(manifest),
|
|
12713
12715
|
from: app.from,
|
|
12714
12716
|
icon: manifest.files.some((f) => f.path === "icon.png"),
|
|
12715
12717
|
installedAt: app.installedAt,
|
|
@@ -21635,14 +21637,17 @@ var db = {
|
|
|
21635
21637
|
}
|
|
21636
21638
|
},
|
|
21637
21639
|
/**
|
|
21638
|
-
* Every message of this profile that carries a task or a
|
|
21640
|
+
* Every message of this profile that carries a task, a routine or a usage card, from every chat and group, oldest first (time,
|
|
21639
21641
|
* then id). Reads the card index only (`CARD_INDEX`): no chat's history is read, however long. The index follows the
|
|
21640
21642
|
* rows, so an edit's card, a deleted message and a deleted chat or group are in step with no bookkeeping.
|
|
21641
21643
|
*/
|
|
21642
21644
|
async getCardMessages() {
|
|
21643
21645
|
const index = (await store(STORES$1.messages, "readonly")).index(CARD_INDEX);
|
|
21644
|
-
|
|
21645
|
-
|
|
21646
|
+
return (await Promise.all([
|
|
21647
|
+
"task",
|
|
21648
|
+
"routine",
|
|
21649
|
+
"usage"
|
|
21650
|
+
].map((kind) => wrap(index.getAll(kind))))).flat().sort((a, b) => a.timestamp - b.timestamp || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
21646
21651
|
},
|
|
21647
21652
|
/** Returns false when the message was already stored. */
|
|
21648
21653
|
async addMessage(message) {
|
|
@@ -22275,12 +22280,28 @@ var Communities = class {
|
|
|
22275
22280
|
now() {
|
|
22276
22281
|
return this.time || Date.now();
|
|
22277
22282
|
}
|
|
22283
|
+
/** Files and voice messages (WISP 503), the desk `Groups` keeps for every group. */
|
|
22284
|
+
files;
|
|
22278
22285
|
has(groupId) {
|
|
22279
22286
|
return this.stored.has(groupId);
|
|
22280
22287
|
}
|
|
22288
|
+
ids() {
|
|
22289
|
+
return [...this.stored.keys()];
|
|
22290
|
+
}
|
|
22281
22291
|
session(groupId) {
|
|
22282
22292
|
return this.live.get(groupId)?.session;
|
|
22283
22293
|
}
|
|
22294
|
+
/** Me in a community I am a member of, for its files: who is in it, and who could read one of its messages. */
|
|
22295
|
+
fileMembership(groupId) {
|
|
22296
|
+
const session = this.live.get(groupId)?.session;
|
|
22297
|
+
if (!session?.isMember) return void 0;
|
|
22298
|
+
return {
|
|
22299
|
+
me: session.myKey,
|
|
22300
|
+
community: true,
|
|
22301
|
+
inRoster: (key) => rosterHas(session.roster, key),
|
|
22302
|
+
couldRead: (key, messageId) => session.couldRead(key, messageId)
|
|
22303
|
+
};
|
|
22304
|
+
}
|
|
22284
22305
|
/** Is this device a hub of the group? (tests and the load harness) */
|
|
22285
22306
|
isHub(groupId) {
|
|
22286
22307
|
const live = this.live.get(groupId);
|
|
@@ -22472,7 +22493,7 @@ var Communities = class {
|
|
|
22472
22493
|
await this.store.putGroup(group);
|
|
22473
22494
|
this.host.emit();
|
|
22474
22495
|
}
|
|
22475
|
-
async send(groupId, text, mentions = [], reply, forwarded, card) {
|
|
22496
|
+
async send(groupId, text, mentions = [], reply, forwarded, card, file) {
|
|
22476
22497
|
const live = this.live.get(groupId);
|
|
22477
22498
|
if (!live) return {
|
|
22478
22499
|
error: "You are not in this group yet",
|
|
@@ -22483,7 +22504,7 @@ var Communities = class {
|
|
|
22483
22504
|
refused: true
|
|
22484
22505
|
};
|
|
22485
22506
|
const now = this.now();
|
|
22486
|
-
const result = card ? await live.session.sendText(text, this.host.myNick?.(), now, mentions, reply, forwarded, card) : await live.session.sendText(text, this.host.myNick?.(), now, mentions, reply, forwarded);
|
|
22507
|
+
const result = card || file ? await live.session.sendText(text, this.host.myNick?.(), now, mentions, reply, forwarded, card, file) : await live.session.sendText(text, this.host.myNick?.(), now, mentions, reply, forwarded);
|
|
22487
22508
|
return "error" in result ? { error: result.error } : {
|
|
22488
22509
|
error: null,
|
|
22489
22510
|
messageId: result.id
|
|
@@ -23587,9 +23608,7 @@ var Communities = class {
|
|
|
23587
23608
|
await this.store.putGroup(group);
|
|
23588
23609
|
},
|
|
23589
23610
|
broadcast: (frame) => this.hearers(id, frame).filter(([, linkId]) => this.sendTo(linkId, frame)).length,
|
|
23590
|
-
direct: (to, frame) =>
|
|
23591
|
-
if (this.mayHear(this.live.get(id), to, frame)) this.sendTo(this.host.edges(id).get(to), frame);
|
|
23592
|
-
},
|
|
23611
|
+
direct: (to, frame) => this.mayHear(this.live.get(id), to, frame) ? this.sendTo(this.host.edges(id).get(to), frame) : void 0,
|
|
23593
23612
|
addressed: (to, frame) => {
|
|
23594
23613
|
const edges = this.host.edges(id), live = this.live.get(id);
|
|
23595
23614
|
const direct = edges.get(to);
|
|
@@ -23604,10 +23623,15 @@ var Communities = class {
|
|
|
23604
23623
|
message: async (m) => {
|
|
23605
23624
|
const timestamp = m.timestamp;
|
|
23606
23625
|
const mentioned = m.sender !== session.myKey && mentionsMember(m.mentions, session.myKey);
|
|
23626
|
+
const file = m.file && this.files ? await this.files.announced(id, {
|
|
23627
|
+
id: m.id,
|
|
23628
|
+
sender: m.sender,
|
|
23629
|
+
file: m.file
|
|
23630
|
+
}, session.myKey) : void 0;
|
|
23607
23631
|
const stored = await this.host.storeMessage({
|
|
23608
23632
|
linkId: MESSAGE_LINK$1(id),
|
|
23609
23633
|
id: m.id,
|
|
23610
|
-
text: m.text,
|
|
23634
|
+
text: file ? groupFileText(m.text, m.file) : m.text,
|
|
23611
23635
|
sender: m.sender === session.myKey ? "me" : "peer",
|
|
23612
23636
|
member: m.sender,
|
|
23613
23637
|
timestamp,
|
|
@@ -23615,7 +23639,8 @@ var Communities = class {
|
|
|
23615
23639
|
...mentionFields(m.mentions, mentioned),
|
|
23616
23640
|
...m.reply && { replyTo: groupReply(m.reply, session.myKey) },
|
|
23617
23641
|
...m.forwarded && { forwarded: m.forwarded },
|
|
23618
|
-
...m.card && { card: m.card }
|
|
23642
|
+
...m.card && { card: m.card },
|
|
23643
|
+
...file && { file }
|
|
23619
23644
|
});
|
|
23620
23645
|
const came = m.sender === session.myKey ? timestamp : cameAt(timestamp, stored, arrivalNow(this.now()));
|
|
23621
23646
|
this.lastMessageAt.set(id, Math.max(this.lastMessageAt.get(id) ?? 0, came));
|
|
@@ -23623,7 +23648,7 @@ var Communities = class {
|
|
|
23623
23648
|
if (m.sender !== session.myKey) this.lastPeerMessageAt.set(id, Math.max(this.lastPeerMessageAt.get(id) ?? 0, came));
|
|
23624
23649
|
noteCame(this.stored.get(id), came, m.sender !== session.myKey, mentioned);
|
|
23625
23650
|
},
|
|
23626
|
-
app: (m) => this.deliver(id, () => this.host.communityApp?.(id, m.sender, m.frame)),
|
|
23651
|
+
app: (m) => this.deliver(id, () => m.frame.t === "group-have" ? this.files?.communityApp(id, m.sender, m.frame) : this.host.communityApp?.(id, m.sender, m.frame)),
|
|
23627
23652
|
pair: (m) => this.deliver(id, () => this.host.communityPair?.(id, m.sender, m.payload)),
|
|
23628
23653
|
changed: () => {
|
|
23629
23654
|
this.membershipChanged(id);
|
|
@@ -23642,7 +23667,11 @@ var Communities = class {
|
|
|
23642
23667
|
adminTurn: () => this.host.adminTurn?.(id) ?? Promise.resolve(true),
|
|
23643
23668
|
relay: (frame) => {
|
|
23644
23669
|
if (this.live.get(id)?.hub) for (const [, linkId] of this.hearers(id, frame)) this.sendTo(linkId, frame);
|
|
23645
|
-
}
|
|
23670
|
+
},
|
|
23671
|
+
...this.host.linkHandled && { handled: (to) => {
|
|
23672
|
+
const edge = this.host.edges(id).get(to);
|
|
23673
|
+
return edge && this.host.linkHandled ? this.host.linkHandled(edge) : Promise.resolve(false);
|
|
23674
|
+
} }
|
|
23646
23675
|
});
|
|
23647
23676
|
this.live.set(id, {
|
|
23648
23677
|
session,
|
|
@@ -24829,6 +24858,8 @@ var Groups = class {
|
|
|
24829
24858
|
typings;
|
|
24830
24859
|
/** Edge signaling through members (WISP 902 · Group Mesh § Signaling through members). */
|
|
24831
24860
|
signals;
|
|
24861
|
+
/** Files and voice messages in every group, private or community (WISP 503 · Group Files). */
|
|
24862
|
+
files;
|
|
24832
24863
|
constructor(host, store = db, timings = ENTRY_TIMINGS, communityTimings = COMMUNITY_TIMINGS, random, hubTimings = MESH_HUB_TIMINGS) {
|
|
24833
24864
|
this.host = host;
|
|
24834
24865
|
this.store = store;
|
|
@@ -24846,6 +24877,27 @@ var Groups = class {
|
|
|
24846
24877
|
stored: (id) => this.stored.get(id),
|
|
24847
24878
|
now: () => this.now()
|
|
24848
24879
|
});
|
|
24880
|
+
const files = host.groupFiles;
|
|
24881
|
+
if (files) {
|
|
24882
|
+
this.files = new GroupFileDesk({
|
|
24883
|
+
...files,
|
|
24884
|
+
send: (linkId, frame) => {
|
|
24885
|
+
try {
|
|
24886
|
+
host.sendOnLink(linkId, frame);
|
|
24887
|
+
return true;
|
|
24888
|
+
} catch {
|
|
24889
|
+
return false;
|
|
24890
|
+
}
|
|
24891
|
+
},
|
|
24892
|
+
edges: (groupId) => host.edges(groupId),
|
|
24893
|
+
ready: (linkId) => host.linkReady(linkId, 6),
|
|
24894
|
+
membership: (groupId) => this.fileMembership(groupId),
|
|
24895
|
+
sendApp: (groupId, frame) => this.communities.sendApp(groupId, frame),
|
|
24896
|
+
changed: () => host.emit(),
|
|
24897
|
+
now: () => this.now()
|
|
24898
|
+
});
|
|
24899
|
+
this.communities.files = this.files;
|
|
24900
|
+
}
|
|
24849
24901
|
}
|
|
24850
24902
|
async load() {
|
|
24851
24903
|
const all = await this.store.getGroups();
|
|
@@ -24863,6 +24915,7 @@ var Groups = class {
|
|
|
24863
24915
|
if (mentionAt) this.lastMentionAt.set(group.id, mentionAt);
|
|
24864
24916
|
}
|
|
24865
24917
|
for (const id of this.sessions.keys()) this.reconcileEdges(id);
|
|
24918
|
+
await this.files?.load(all.map((g) => g.id));
|
|
24866
24919
|
for (const group of this.stored.values()) for (const [, linkId] of this.host.entries(group.id)) if (!group.invitation?.entry || group.invitation.linkId !== linkId) await this.host.closeEdge(linkId);
|
|
24867
24920
|
}
|
|
24868
24921
|
/** A group's pin now (WISP 400 § Pinned message), the latest one taken, unpinned or not. */
|
|
@@ -25107,6 +25160,58 @@ var Groups = class {
|
|
|
25107
25160
|
};
|
|
25108
25161
|
}
|
|
25109
25162
|
/**
|
|
25163
|
+
* Announces a file or voice message of mine (WISP 503): `text` is its caption, or the line older apps show
|
|
25164
|
+
* (`groupFileFallback`), `file` its description with the digest of the bytes kept under `fileId`, which this device
|
|
25165
|
+
* then holds and serves. `messageId` is the announcement's.
|
|
25166
|
+
*/
|
|
25167
|
+
async sendFile(groupId, text, file, fileId, reply, forwarded) {
|
|
25168
|
+
if (!this.files) return {
|
|
25169
|
+
error: "This app takes no group files",
|
|
25170
|
+
refused: true
|
|
25171
|
+
};
|
|
25172
|
+
const paced = this.files.mayAnnounce(groupId);
|
|
25173
|
+
if (paced) return { error: paced };
|
|
25174
|
+
this.files.outgoing(groupId, fileId, file);
|
|
25175
|
+
let result;
|
|
25176
|
+
if (this.isCommunity(groupId)) result = await this.communities.send(groupId, text, [], reply, forwarded, void 0, file);
|
|
25177
|
+
else {
|
|
25178
|
+
const session = this.sessions.get(groupId);
|
|
25179
|
+
const sent = session ? await session.sendText(text, Date.now(), [], reply, forwarded, void 0, file) : { error: "You are not in this group yet" };
|
|
25180
|
+
if (session) this.typings.say(session, false);
|
|
25181
|
+
result = "error" in sent ? { error: sent.error } : {
|
|
25182
|
+
error: null,
|
|
25183
|
+
messageId: sent.id
|
|
25184
|
+
};
|
|
25185
|
+
}
|
|
25186
|
+
if (result.error) this.files.notSent(groupId, file);
|
|
25187
|
+
return result;
|
|
25188
|
+
}
|
|
25189
|
+
/** The person asks for a group file this device did not fetch by itself, or asks again for one that stopped coming. */
|
|
25190
|
+
downloadFile(fileId) {
|
|
25191
|
+
if (!groupOfFile(fileId, [...this.stored.keys(), ...this.communities.ids()]) || !this.files) throw new Error("No such file");
|
|
25192
|
+
return this.files.download(fileId);
|
|
25193
|
+
}
|
|
25194
|
+
/** An edge's files/3 session opened or closed (WISP 503: the bytes of a group's files go over it). */
|
|
25195
|
+
fileSession(groupId, peerKey, linkId, open) {
|
|
25196
|
+
this.files?.session(groupId, peerKey, linkId, open);
|
|
25197
|
+
}
|
|
25198
|
+
/** A files/3 frame on an edge, from the member it is pinned to. */
|
|
25199
|
+
filesFrame(groupId, peerKey, linkId, frame) {
|
|
25200
|
+
return this.files?.filesFrame(groupId, peerKey, linkId, frame) ?? Promise.resolve();
|
|
25201
|
+
}
|
|
25202
|
+
/** Me in a group I am active in, for its files: who is in it, and who could read one of its messages. */
|
|
25203
|
+
fileMembership(groupId) {
|
|
25204
|
+
if (this.isCommunity(groupId)) return this.communities.fileMembership(groupId);
|
|
25205
|
+
const session = this.sessions.get(groupId);
|
|
25206
|
+
if (session?.status !== "active") return void 0;
|
|
25207
|
+
return {
|
|
25208
|
+
me: session.myKey,
|
|
25209
|
+
community: false,
|
|
25210
|
+
inRoster: (key) => rosterHas(session.roster, key),
|
|
25211
|
+
couldRead: (key, messageId) => session.couldRead(key, messageId)
|
|
25212
|
+
};
|
|
25213
|
+
}
|
|
25214
|
+
/**
|
|
25110
25215
|
* This side is typing in a private group (with what it is doing), or stopped (WISP 902 · Group Mesh § Typing).
|
|
25111
25216
|
* Nothing for a community: it does not carry typing yet.
|
|
25112
25217
|
*/
|
|
@@ -25347,7 +25452,11 @@ var Groups = class {
|
|
|
25347
25452
|
await this.session(groupId).rename(name);
|
|
25348
25453
|
}
|
|
25349
25454
|
async forget(groupId) {
|
|
25350
|
-
if (this.isCommunity(groupId))
|
|
25455
|
+
if (this.isCommunity(groupId)) {
|
|
25456
|
+
await this.communities.forget(groupId);
|
|
25457
|
+
await this.files?.drop(groupId);
|
|
25458
|
+
return;
|
|
25459
|
+
}
|
|
25351
25460
|
if (this.sessions.get(groupId)?.status === "active") try {
|
|
25352
25461
|
await this.leave(groupId);
|
|
25353
25462
|
} catch {}
|
|
@@ -25374,6 +25483,7 @@ var Groups = class {
|
|
|
25374
25483
|
this.signals.forget(groupId);
|
|
25375
25484
|
for (const linkId of [...this.host.edges(groupId).values(), ...this.host.entries(groupId).values()]) await this.host.closeEdge(linkId);
|
|
25376
25485
|
await this.store.deleteGroup(groupId);
|
|
25486
|
+
await this.files?.drop(groupId);
|
|
25377
25487
|
this.host.historyGone?.(groupId);
|
|
25378
25488
|
this.host.membersChanged?.(groupId);
|
|
25379
25489
|
this.host.emit();
|
|
@@ -25499,6 +25609,7 @@ var Groups = class {
|
|
|
25499
25609
|
this.meshTick(now);
|
|
25500
25610
|
await this.communities.tick(now);
|
|
25501
25611
|
await this.hubsTick(now);
|
|
25612
|
+
this.files?.tick();
|
|
25502
25613
|
} finally {
|
|
25503
25614
|
this.ticking = false;
|
|
25504
25615
|
}
|
|
@@ -25872,6 +25983,12 @@ var Groups = class {
|
|
|
25872
25983
|
}
|
|
25873
25984
|
/** A `group-*` frame on an edge: from the member the edge is pinned to. */
|
|
25874
25985
|
async handleEdgeFrame(groupId, peerKey, frame) {
|
|
25986
|
+
const t = frame && typeof frame === "object" ? frame.t : void 0;
|
|
25987
|
+
if (t === "group-want" || t === "group-want-no" || t === "group-have") {
|
|
25988
|
+
const linkId = this.host.edges(groupId).get(peerKey);
|
|
25989
|
+
if (linkId && !this.stored.get(groupId)?.left) await this.files?.frame(groupId, peerKey, linkId, frame);
|
|
25990
|
+
return;
|
|
25991
|
+
}
|
|
25875
25992
|
if (this.isCommunity(groupId)) return this.communities.handleEdgeFrame(groupId, peerKey, frame);
|
|
25876
25993
|
if (this.stored.get(groupId)?.left) {
|
|
25877
25994
|
if (this.removesMe(groupId, peerKey, frame)) await this.leaveConfirmed(groupId);
|
|
@@ -25900,7 +26017,6 @@ var Groups = class {
|
|
|
25900
26017
|
return;
|
|
25901
26018
|
}
|
|
25902
26019
|
if (!session) return;
|
|
25903
|
-
const t = frame && typeof frame === "object" ? frame.t : void 0;
|
|
25904
26020
|
if (t === "group-react" || t === "group-reacted") {
|
|
25905
26021
|
await this.reaction(groupId, session, peerKey, frame);
|
|
25906
26022
|
return;
|
|
@@ -25961,6 +26077,7 @@ var Groups = class {
|
|
|
25961
26077
|
edgeReady(groupId, peerKey, linkId) {
|
|
25962
26078
|
if (this.isCommunity(groupId)) {
|
|
25963
26079
|
this.communities.edgeReady(groupId, peerKey, linkId);
|
|
26080
|
+
this.files?.edgeReady(groupId, peerKey, linkId);
|
|
25964
26081
|
return;
|
|
25965
26082
|
}
|
|
25966
26083
|
const left = this.stored.get(groupId)?.left;
|
|
@@ -26000,6 +26117,7 @@ var Groups = class {
|
|
|
26000
26117
|
this.welcomed.get(groupId)?.delete(peerKey);
|
|
26001
26118
|
this.announceHere(groupId, session, peerKey);
|
|
26002
26119
|
this.signals.edgeReady(groupId, session, peerKey, linkId);
|
|
26120
|
+
this.files?.edgeReady(groupId, peerKey, linkId);
|
|
26003
26121
|
this.host.edgeUp?.(groupId, peerKey);
|
|
26004
26122
|
this.host.emit();
|
|
26005
26123
|
}
|
|
@@ -26201,10 +26319,15 @@ var Groups = class {
|
|
|
26201
26319
|
message: async (m) => {
|
|
26202
26320
|
const timestamp = m.timestamp;
|
|
26203
26321
|
const mentioned = m.sender !== session.myKey && mentionsMember(m.mentions, session.myKey);
|
|
26322
|
+
const file = m.file && this.files ? await this.files.announced(state.id, {
|
|
26323
|
+
id: m.id,
|
|
26324
|
+
sender: m.sender,
|
|
26325
|
+
file: m.file
|
|
26326
|
+
}, session.myKey) : void 0;
|
|
26204
26327
|
const message = {
|
|
26205
26328
|
linkId: MESSAGE_LINK(state.id),
|
|
26206
26329
|
id: m.id,
|
|
26207
|
-
text: m.text,
|
|
26330
|
+
text: file ? groupFileText(m.text, m.file) : m.text,
|
|
26208
26331
|
sender: m.sender === session.myKey ? "me" : "peer",
|
|
26209
26332
|
member: m.sender,
|
|
26210
26333
|
timestamp,
|
|
@@ -26212,7 +26335,8 @@ var Groups = class {
|
|
|
26212
26335
|
...mentionFields(m.mentions, mentioned),
|
|
26213
26336
|
...m.reply && { replyTo: groupReply(m.reply, session.myKey) },
|
|
26214
26337
|
...m.forwarded && { forwarded: m.forwarded },
|
|
26215
|
-
...m.card && { card: m.card }
|
|
26338
|
+
...m.card && { card: m.card },
|
|
26339
|
+
...file && { file }
|
|
26216
26340
|
};
|
|
26217
26341
|
const stored = m.completes && this.host.completeMessage ? (await this.host.completeMessage(message), false) : await this.host.storeMessage(message);
|
|
26218
26342
|
const came = m.sender === session.myKey ? timestamp : cameAt(timestamp, stored, arrivalNow(this.now()));
|
|
@@ -26229,7 +26353,11 @@ var Groups = class {
|
|
|
26229
26353
|
},
|
|
26230
26354
|
metaChanged: (by, change, at) => {
|
|
26231
26355
|
this.metaChanged(state.id, session, by, change, at);
|
|
26232
|
-
}
|
|
26356
|
+
},
|
|
26357
|
+
...this.host.linkHandled && { handled: (to) => {
|
|
26358
|
+
const edge = this.host.edges(state.id).get(to);
|
|
26359
|
+
return edge && this.host.linkHandled ? this.host.linkHandled(edge) : Promise.resolve(false);
|
|
26360
|
+
} }
|
|
26233
26361
|
});
|
|
26234
26362
|
this.sessions.set(state.id, session);
|
|
26235
26363
|
this.lastRoster.set(state.id, session.roster);
|
|
@@ -26341,16 +26469,34 @@ function edgeView(live, now = Date.now(), noSlot = false) {
|
|
|
26341
26469
|
noSlot: true,
|
|
26342
26470
|
lastSeenAt: Math.max(presenceSeenAt(live.presence), live.lastSyncAt)
|
|
26343
26471
|
};
|
|
26344
|
-
const
|
|
26472
|
+
const pairingError = live.pairing?.status === "error" ? live.pairing.error : void 0;
|
|
26473
|
+
const error = open ? void 0 : pairingError ?? live.discoveryError;
|
|
26345
26474
|
const state = open ? "open" : error ? "error" : live.dataLink !== "idle" ? "connecting" : "waiting";
|
|
26346
26475
|
return {
|
|
26347
26476
|
linkId: live.stored.id,
|
|
26348
26477
|
state,
|
|
26349
26478
|
...open && live.pairing?.transport ? { transport: live.pairing.transport } : {},
|
|
26350
26479
|
lastSeenAt: open ? now : Math.max(presenceSeenAt(live.presence), live.lastSyncAt),
|
|
26351
|
-
...error ? {
|
|
26480
|
+
...error ? {
|
|
26481
|
+
error,
|
|
26482
|
+
cause: pairingError ? edgeErrorCause(pairingError) : "relays"
|
|
26483
|
+
} : {}
|
|
26352
26484
|
};
|
|
26353
26485
|
}
|
|
26486
|
+
/**
|
|
26487
|
+
* A pairing error the session ends with when nothing is wrong with the member: an attempt dialled again. Their
|
|
26488
|
+
* English is `PairedSession`'s and `GhostLink`'s (packages/core).
|
|
26489
|
+
*/
|
|
26490
|
+
var SESSION_DROPS = [
|
|
26491
|
+
/^Session receive queue full$/,
|
|
26492
|
+
/^The peer did not finish authentication\b/,
|
|
26493
|
+
/^Invalid session negotiation$/
|
|
26494
|
+
];
|
|
26495
|
+
/** What an edge's pairing error comes down to: the relays (its signal could not go out), a session dropped, or else. */
|
|
26496
|
+
function edgeErrorCause(error) {
|
|
26497
|
+
if (error.startsWith("Could not publish connection details:") || /^Could not (publish|read) discovery:/.test(error)) return "relays";
|
|
26498
|
+
return SESSION_DROPS.some((drop) => drop.test(error)) ? "session" : "other";
|
|
26499
|
+
}
|
|
26354
26500
|
//#endregion
|
|
26355
26501
|
//#region ../browser/src/engine/groupPayments.ts
|
|
26356
26502
|
/**
|
|
@@ -33441,6 +33587,12 @@ var DEFAULT_SETTINGS = {
|
|
|
33441
33587
|
mints: [],
|
|
33442
33588
|
mintsInitialized: false
|
|
33443
33589
|
};
|
|
33590
|
+
/**
|
|
33591
|
+
* A card that went on the DHT floor or into a hold goes again live (`restoreCards`) only for the newest this many
|
|
33592
|
+
* messages of a chat, sent within this long: an old chat's cards are not worth a burst of edits.
|
|
33593
|
+
*/
|
|
33594
|
+
var CARD_RESTORE_MOST = 50;
|
|
33595
|
+
var CARD_RESTORE_MS = 6048e5;
|
|
33444
33596
|
/** A reaction said on the live session and not confirmed is said again after this long. */
|
|
33445
33597
|
var REACTION_RESEND_MS = 3e4;
|
|
33446
33598
|
function emptyLinkFiles() {
|
|
@@ -34407,6 +34559,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
34407
34559
|
live.link.sendGroupFrame(frame);
|
|
34408
34560
|
},
|
|
34409
34561
|
linkReady: (linkId, version = 1) => !!this.links.get(linkId)?.link?.supportsGroupVersion(version),
|
|
34562
|
+
linkHandled: (linkId) => this.links.get(linkId)?.link?.handled() ?? Promise.resolve(false),
|
|
34410
34563
|
linkOpen: (linkId) => !!this.links.get(linkId)?.link?.isDataLinkOpen,
|
|
34411
34564
|
myNick: () => this.sharedNick,
|
|
34412
34565
|
seqFloor: () => this.counterFloor,
|
|
@@ -34482,7 +34635,17 @@ var GhostlyNode = class GhostlyNode {
|
|
|
34482
34635
|
this.groupEdits.resend(groupId, peer).catch(() => {});
|
|
34483
34636
|
this.sendGroupPinFrame(groupId, peer);
|
|
34484
34637
|
},
|
|
34485
|
-
communityPair: (groupId, sender, payload) => this.communityPay.receivePair(groupId, sender, payload)
|
|
34638
|
+
communityPair: (groupId, sender, payload) => this.communityPay.receivePair(groupId, sender, payload),
|
|
34639
|
+
groupFiles: {
|
|
34640
|
+
store: storedGroupFiles,
|
|
34641
|
+
transfers: this.transfers,
|
|
34642
|
+
settings: () => ({
|
|
34643
|
+
autoDownloads: this.settings.autoDownloads !== false,
|
|
34644
|
+
serveFiles: this.settings.serveFiles !== false
|
|
34645
|
+
}),
|
|
34646
|
+
sendFiles: (linkId, frame) => this.links.get(linkId)?.link?.sendFilesFrame(frame) ?? false,
|
|
34647
|
+
writable: (linkId) => this.links.get(linkId)?.link?.filesWritable()
|
|
34648
|
+
}
|
|
34486
34649
|
});
|
|
34487
34650
|
/**
|
|
34488
34651
|
* Payments in community groups (WISP 903 · Group Community § Payments): the desk pays and requests over a link
|
|
@@ -35531,7 +35694,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
35531
35694
|
async statusCardIndex() {
|
|
35532
35695
|
const rows = [];
|
|
35533
35696
|
for (const m of await db.getCardMessages()) {
|
|
35534
|
-
if (m.card?.kind !== "task" && m.card?.kind !== "routine") continue;
|
|
35697
|
+
if (m.card?.kind !== "task" && m.card?.kind !== "routine" && m.card?.kind !== "usage") continue;
|
|
35535
35698
|
rows.push({
|
|
35536
35699
|
linkId: m.linkId,
|
|
35537
35700
|
id: m.id,
|
|
@@ -36448,7 +36611,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
36448
36611
|
delivery: "sending",
|
|
36449
36612
|
...preview && { preview },
|
|
36450
36613
|
...answers,
|
|
36451
|
-
...card
|
|
36614
|
+
...card && { cardRestore: "due" }
|
|
36452
36615
|
});
|
|
36453
36616
|
this.hold.hold(linkId, {
|
|
36454
36617
|
kind: "text",
|
|
@@ -36660,34 +36823,49 @@ var GhostlyNode = class GhostlyNode {
|
|
|
36660
36823
|
return !!live?.caps?.peer?.capabilities.includes(EDIT_CAPABILITY);
|
|
36661
36824
|
}
|
|
36662
36825
|
/**
|
|
36663
|
-
* A
|
|
36664
|
-
* Buttons):
|
|
36826
|
+
* A card of mine went on the DHT floor or into a hold, which carry its text alone (WISP 405 · Status Cards, WISP 406 ·
|
|
36827
|
+
* Message Buttons): the card is due to go again live. Once: a row that had it restored already stays so.
|
|
36665
36828
|
*/
|
|
36666
|
-
async
|
|
36667
|
-
if (message.card
|
|
36668
|
-
await db.patchMessage(linkId, message.id, (current) => current.sender === "me" && current.card
|
|
36829
|
+
async cardWentBare(linkId, message) {
|
|
36830
|
+
if (!message.card || message.cardRestore || message.buttonsRestore) return;
|
|
36831
|
+
await db.patchMessage(linkId, message.id, (current) => current.sender === "me" && current.card && !current.cardRestore && !current.buttonsRestore ? { cardRestore: "due" } : null);
|
|
36832
|
+
}
|
|
36833
|
+
/** The session capability a contact's app lists when it shows a card of this kind, so a restore edit reads as a card there. */
|
|
36834
|
+
static cardCapability(card) {
|
|
36835
|
+
return card.kind === "buttons" ? BUTTONS_CAPABILITY : card.kind === "app" ? APPS_CAPABILITY : STATUS_CARD_CAPABILITY;
|
|
36669
36836
|
}
|
|
36670
36837
|
/**
|
|
36671
|
-
*
|
|
36672
|
-
* hold reached the contact as text alone, and a copy under the same id that comes live later
|
|
36673
|
-
* already there.
|
|
36674
|
-
*
|
|
36675
|
-
* message
|
|
36676
|
-
*
|
|
36677
|
-
*
|
|
36678
|
-
*
|
|
36838
|
+
* Cards the contact may not have (WISP 405 · Status Cards, WISP 406 · Message Buttons): a message whose text went on
|
|
36839
|
+
* the DHT floor or into a hold reached the contact as text alone, and a copy under the same id that comes live later
|
|
36840
|
+
* is taken as the one already there. An app card or a finished task gets no later update to carry its card. So once
|
|
36841
|
+
* the chat is live with an app that shows the kind (`buttons/1`, `status-card/1`, `apps/1`) and takes edits, each such
|
|
36842
|
+
* message goes again as an edit of its card alone: the same text, so no version and no edit mark. Once per message (the
|
|
36843
|
+
* row says so, across restarts); the edit queue carries it (a card's edit goes only live, and a later edit of the
|
|
36844
|
+
* author's replaces it, the highest number winning). A contact whose app does not show the kind (it would drop the card
|
|
36845
|
+
* and mark the text edited) gets none: the row waits for one that does. Bounded: the newest `CARD_RESTORE_MOST` such
|
|
36846
|
+
* messages of the last `CARD_RESTORE_MS`; older ones are given up. The edit is marked `restore`: the bot's own event
|
|
36847
|
+
* stream says nothing of it, while its number counts, so a later edit of the bot's takes the next one.
|
|
36679
36848
|
*/
|
|
36680
|
-
async
|
|
36849
|
+
async restoreCards(linkId) {
|
|
36681
36850
|
const live = this.links.get(linkId), link = live?.link;
|
|
36682
|
-
|
|
36683
|
-
|
|
36851
|
+
const offers = link?.sessionOffers.peer;
|
|
36852
|
+
if (!live?.stored.profile || live.stored.group || !link?.supportsEdits || !offers) return;
|
|
36853
|
+
const due = (await db.getMessages(linkId)).filter((m) => m.sender === "me" && (m.cardRestore ?? m.buttonsRestore) === "due").sort((a, b) => b.timestamp - a.timestamp);
|
|
36854
|
+
const since = Date.now() - CARD_RESTORE_MS;
|
|
36684
36855
|
const changed = [];
|
|
36685
|
-
for (const message of due) {
|
|
36856
|
+
for (const [index, message] of due.entries()) {
|
|
36857
|
+
const stale = index >= CARD_RESTORE_MOST || message.timestamp < since;
|
|
36858
|
+
if (!stale && message.card && !offers.includes(GhostlyNode.cardCapability(message.card))) continue;
|
|
36686
36859
|
const at = Date.now();
|
|
36687
36860
|
if ((await db.patchMessage(linkId, message.id, (current) => {
|
|
36688
|
-
if (current.sender !== "me" || current.buttonsRestore !== "due") return null;
|
|
36861
|
+
if (current.sender !== "me" || (current.cardRestore ?? current.buttonsRestore) !== "due") return null;
|
|
36862
|
+
const done = {
|
|
36863
|
+
cardRestore: "sent",
|
|
36864
|
+
buttonsRestore: void 0
|
|
36865
|
+
};
|
|
36689
36866
|
const seq = (current.edit?.seq ?? 0) + 1;
|
|
36690
|
-
if (current.card
|
|
36867
|
+
if (stale || !current.card || !current.wireId || seq > STATUS_CARD_LIMITS.edits) return done;
|
|
36868
|
+
if (current.edit?.pending) return done;
|
|
36691
36869
|
return {
|
|
36692
36870
|
edit: {
|
|
36693
36871
|
...withEdit(current, {
|
|
@@ -36700,7 +36878,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
36700
36878
|
}).edit,
|
|
36701
36879
|
restore: true
|
|
36702
36880
|
},
|
|
36703
|
-
|
|
36881
|
+
...done
|
|
36704
36882
|
};
|
|
36705
36883
|
}))?.edit?.pending) changed.push(message.id);
|
|
36706
36884
|
}
|
|
@@ -36806,7 +36984,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
36806
36984
|
const reply = GhostlyNode.wireReply(message);
|
|
36807
36985
|
const error = message.card ? await link.sendMessage(message.text, message.timestamp, message.wireId, message.preview, reply, message.forwarded, message.card) : message.forwarded ? await link.sendMessage(message.text, message.timestamp, message.wireId, message.preview, reply, message.forwarded) : reply ? await link.sendMessage(message.text, message.timestamp, message.wireId, message.preview, reply) : await link.sendMessage(message.text, message.timestamp, message.wireId, ...message.preview ? [message.preview] : []);
|
|
36808
36986
|
await this.noteTextSend(linkId, message, snapshot, at, error, link);
|
|
36809
|
-
if (!error && message.via === "pkarr") await this.
|
|
36987
|
+
if (!error && message.via === "pkarr") await this.cardWentBare(linkId, message);
|
|
36810
36988
|
return error;
|
|
36811
36989
|
}, (message) => message.via === "pkarr" ? DHT_MESSAGE_TTL : 2e4, (message) => {
|
|
36812
36990
|
const pending = this.links.get(linkId)?.stored.dhtDeliveryState?.pending;
|
|
@@ -36831,7 +37009,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
36831
37009
|
via: "hold",
|
|
36832
37010
|
resendUntil: void 0
|
|
36833
37011
|
});
|
|
36834
|
-
await this.
|
|
37012
|
+
await this.cardWentBare(linkId, message);
|
|
36835
37013
|
await this.messagesChanged(linkId, [message.id]);
|
|
36836
37014
|
this.hold.hold(linkId, {
|
|
36837
37015
|
kind: "text",
|
|
@@ -37264,6 +37442,11 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37264
37442
|
return offered;
|
|
37265
37443
|
}
|
|
37266
37444
|
fileAction({ linkId, fileId, action }) {
|
|
37445
|
+
if (typeof linkId === "string" && linkId.startsWith("group:")) {
|
|
37446
|
+
if (action !== "accept" && action !== "request") throw new Error("A group's file is downloaded (accept) or asked for again (request)");
|
|
37447
|
+
if (!fileId.startsWith(`group-${linkId.slice(6)}-`)) throw new Error("No such file in this group");
|
|
37448
|
+
return this.groups.downloadFile(fileId);
|
|
37449
|
+
}
|
|
37267
37450
|
const live = this.links.get(linkId);
|
|
37268
37451
|
if (!live) throw new Error("No such chat");
|
|
37269
37452
|
if (![
|
|
@@ -37872,6 +38055,58 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37872
38055
|
if (!sent.error) this.wakeMentioned(groupId, text, named);
|
|
37873
38056
|
return sent;
|
|
37874
38057
|
}
|
|
38058
|
+
/**
|
|
38059
|
+
* Announces a file or voice message in a group (WISP 503 · Group Files): its bytes are in the files store under
|
|
38060
|
+
* `file.id` (`group-<group>-out-…`), put there by the caller as a chat's file is (`sendFile`). The message carries its
|
|
38061
|
+
* description and the SHA-256 of those bytes; its text is `caption`, or the line apps from before show. This device
|
|
38062
|
+
* is its first holder: members fetch it from here, then from each other.
|
|
38063
|
+
*/
|
|
38064
|
+
async sendGroupFile({ groupId, file, caption, replyTo, forwarded: hops }) {
|
|
38065
|
+
if (typeof groupId !== "string" || !file || typeof file.id !== "string" || !file.id.startsWith(`group-${groupId}-out-`)) return {
|
|
38066
|
+
error: "Invalid file id",
|
|
38067
|
+
refused: true
|
|
38068
|
+
};
|
|
38069
|
+
if (file.size > GROUP_FILE_LIMITS.maxBytes) return {
|
|
38070
|
+
error: `A group takes files of up to ${formatFileSize(GROUP_FILE_LIMITS.maxBytes)}`,
|
|
38071
|
+
refused: true
|
|
38072
|
+
};
|
|
38073
|
+
const stored = await fileStore.get(file.id);
|
|
38074
|
+
if (!stored?.metadata || stored.linkId !== `group:${groupId}` || stored.direction !== "out") return {
|
|
38075
|
+
error: "The file is gone",
|
|
38076
|
+
refused: true
|
|
38077
|
+
};
|
|
38078
|
+
if (storedSize(stored) !== file.size) return {
|
|
38079
|
+
error: "The file is not all here",
|
|
38080
|
+
refused: true
|
|
38081
|
+
};
|
|
38082
|
+
const digest = stored.digest ?? (stored.blob ? await blobDigest(stored.blob) : await (await fileBytesOf(stored.bytes)).digest(file.id));
|
|
38083
|
+
if (!stored.digest) await fileStore.patch(file.id, { digest });
|
|
38084
|
+
const meta = readGroupFileMeta({
|
|
38085
|
+
name: file.name,
|
|
38086
|
+
mime: file.mime,
|
|
38087
|
+
size: file.size,
|
|
38088
|
+
d: digest,
|
|
38089
|
+
...file.voice && { voice: file.voice },
|
|
38090
|
+
...file.video && { video: file.video },
|
|
38091
|
+
...file.image && { image: file.image }
|
|
38092
|
+
});
|
|
38093
|
+
if (!meta) return {
|
|
38094
|
+
error: "That file cannot go to a group",
|
|
38095
|
+
refused: true
|
|
38096
|
+
};
|
|
38097
|
+
if (file.voice && !meta.voice) return {
|
|
38098
|
+
error: "That recording cannot be sent as a voice message",
|
|
38099
|
+
refused: true
|
|
38100
|
+
};
|
|
38101
|
+
const reply = replyTo === void 0 ? void 0 : await this.replyFor(`group:${groupId}`, replyTo);
|
|
38102
|
+
if (typeof reply === "string") return { error: reply };
|
|
38103
|
+
const text = typeof caption === "string" && caption.trim() ? caption : groupFileFallback(meta);
|
|
38104
|
+
return this.groups.sendFile(groupId, text, meta, file.id, reply && {
|
|
38105
|
+
i: reply.id,
|
|
38106
|
+
s: reply.snippet,
|
|
38107
|
+
f: reply.member
|
|
38108
|
+
}, readForwarded(hops));
|
|
38109
|
+
}
|
|
37875
38110
|
groupMessages({ groupId }) {
|
|
37876
38111
|
return this.groups.messages(groupId);
|
|
37877
38112
|
}
|
|
@@ -37986,7 +38221,30 @@ var GhostlyNode = class GhostlyNode {
|
|
|
37986
38221
|
continue;
|
|
37987
38222
|
}
|
|
37988
38223
|
if (groupId) {
|
|
37989
|
-
|
|
38224
|
+
const original = message.file, file = {
|
|
38225
|
+
id: groupFileId(groupId, "out"),
|
|
38226
|
+
name: original.name,
|
|
38227
|
+
size: original.size,
|
|
38228
|
+
mime: original.mime,
|
|
38229
|
+
...original.voice && { voice: original.voice },
|
|
38230
|
+
...original.video && { video: original.video },
|
|
38231
|
+
...original.image && { image: original.image }
|
|
38232
|
+
};
|
|
38233
|
+
await copyForForward(original.id, {
|
|
38234
|
+
linkId: to,
|
|
38235
|
+
wireId: file.id.slice(`group-${groupId}-out-`.length),
|
|
38236
|
+
timestamp: now(),
|
|
38237
|
+
file
|
|
38238
|
+
});
|
|
38239
|
+
const sent = await this.sendGroupFile({
|
|
38240
|
+
groupId,
|
|
38241
|
+
file,
|
|
38242
|
+
forwarded: hops
|
|
38243
|
+
});
|
|
38244
|
+
if (sent.error) {
|
|
38245
|
+
await removeStored(file.id).catch(() => {});
|
|
38246
|
+
note(sent.error);
|
|
38247
|
+
} else if (sent.messageId) result.messageIds.push(sent.messageId);
|
|
37990
38248
|
continue;
|
|
37991
38249
|
}
|
|
37992
38250
|
if (!live.link) {
|
|
@@ -39448,6 +39706,12 @@ var GhostlyNode = class GhostlyNode {
|
|
|
39448
39706
|
this.groups.stopTyping();
|
|
39449
39707
|
}
|
|
39450
39708
|
}
|
|
39709
|
+
for (const key of ["autoDownloads", "serveFiles"]) {
|
|
39710
|
+
if (settings[key] === void 0) continue;
|
|
39711
|
+
if (settings[key] !== false) delete this.settings[key];
|
|
39712
|
+
else this.settings[key] = false;
|
|
39713
|
+
await db.putSettings(this.settings);
|
|
39714
|
+
}
|
|
39451
39715
|
if (settings.publicProfiles !== void 0) {
|
|
39452
39716
|
if (settings.publicProfiles !== false) delete this.settings.publicProfiles;
|
|
39453
39717
|
else this.settings.publicProfiles = false;
|
|
@@ -40455,6 +40719,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
40455
40719
|
arkPaymentsSupport: !entry,
|
|
40456
40720
|
usdtPaymentsSupport: !entry,
|
|
40457
40721
|
barkPaymentsSupport: !entry,
|
|
40722
|
+
largeFilesSupport: !entry,
|
|
40458
40723
|
params: stored,
|
|
40459
40724
|
rtcAvailable: typeof RTCPeerConnection !== "undefined" && !this.edgeRtcOff.has(linkId),
|
|
40460
40725
|
resume: !entry && stored.edgeLive ? resumeOn : void 0,
|
|
@@ -40531,7 +40796,9 @@ var GhostlyNode = class GhostlyNode {
|
|
|
40531
40796
|
onPaymentAsk: (ask) => member() ? this.desk.onPaymentAsk(linkId, ask) : void 0,
|
|
40532
40797
|
onPayment: (payment) => member() ? this.desk.onPayment(linkId, payment) : void 0,
|
|
40533
40798
|
onPaymentResult: (result) => member() ? this.desk.onPaymentResult(linkId, result) : void 0,
|
|
40534
|
-
onPeerNick: (nick) => this.groups.edgeNick(group, peer, nick ?? void 0)
|
|
40799
|
+
onPeerNick: (nick) => this.groups.edgeNick(group, peer, nick ?? void 0),
|
|
40800
|
+
onFilesFrame: (frame) => member() ? this.groups.filesFrame(group, peer, linkId, frame) : void 0,
|
|
40801
|
+
onFilesSession: (open) => this.groups.fileSession(group, peer, linkId, open)
|
|
40535
40802
|
},
|
|
40536
40803
|
onPresence: (presence) => {
|
|
40537
40804
|
if (presence.online && !seen) {
|
|
@@ -40833,7 +41100,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
40833
41100
|
onMessageEdit: stored.profile && !stored.group ? (edit) => this.receiveEdit(linkId, edit) : void 0,
|
|
40834
41101
|
onEditReceipt: stored.profile && !stored.group ? (id, e) => this.editsFor(linkId).received(id, e) : void 0,
|
|
40835
41102
|
onEditSupport: (supported) => {
|
|
40836
|
-
if (supported && stored.profile && !stored.group) this.
|
|
41103
|
+
if (supported && stored.profile && !stored.group) this.restoreCards(linkId).catch(() => {}).then(() => this.editsFor(linkId).flush()).catch(() => {});
|
|
40837
41104
|
},
|
|
40838
41105
|
onWakeSupport: (supported) => {
|
|
40839
41106
|
if (supported && stored.profile && !stored.group) this.shareWake(linkId);
|
|
@@ -41101,7 +41368,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
41101
41368
|
get nativeFactories() {
|
|
41102
41369
|
const relay = this.relaysHyperdht ? this.hyperdhtRelay : "";
|
|
41103
41370
|
return {
|
|
41104
|
-
...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-
|
|
41371
|
+
...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-CnEkFl8R.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
|
|
41105
41372
|
...this.options.nativeTransports,
|
|
41106
41373
|
...this.ownIrohRelays ? { "iroh/1": (seedB64) => this.options.nativeTransports["iroh/1"](seedB64, this.settings.irohRelays?.length ? { relays: [...this.settings.irohRelays] } : {}) } : {},
|
|
41107
41374
|
...this.options.irohWeb ? { "iroh/1": (seedB64) => createIrohWebEndpoint(seedB64, { relays: this.irohRelays }) } : {}
|
|
@@ -41369,12 +41636,16 @@ var GhostlyNode = class GhostlyNode {
|
|
|
41369
41636
|
...whole.replyTo && { replyTo: whole.replyTo },
|
|
41370
41637
|
...whole.forwarded && { forwarded: whole.forwarded },
|
|
41371
41638
|
...whole.card && { card: whole.card },
|
|
41372
|
-
...whole.press && { press: whole.press }
|
|
41639
|
+
...whole.press && { press: whole.press },
|
|
41640
|
+
...whole.file && {
|
|
41641
|
+
file: whole.file,
|
|
41642
|
+
text: whole.text
|
|
41643
|
+
}
|
|
41373
41644
|
};
|
|
41374
41645
|
if (!Object.keys(added).length) return;
|
|
41375
41646
|
if (!await db.patchMessage(message.linkId, message.id, (stored) => {
|
|
41376
41647
|
if (stored.member !== message.member || stored.sender !== message.sender) return null;
|
|
41377
|
-
const { card: _card, ...rest } = added;
|
|
41648
|
+
const { card: _card, text: _text, ...rest } = added;
|
|
41378
41649
|
return stored.edit ? rest : added;
|
|
41379
41650
|
})) return;
|
|
41380
41651
|
await this.messagesChanged(message.linkId, [message.id]);
|
|
@@ -41844,4 +42115,4 @@ function signalKind(signal) {
|
|
|
41844
42115
|
//#endregion
|
|
41845
42116
|
export { EngineServer, hyperdhtRelayProblem as n, server_exports as t };
|
|
41846
42117
|
|
|
41847
|
-
//# sourceMappingURL=server-
|
|
42118
|
+
//# sourceMappingURL=server-HMzdsyn_.js.map
|