@ghostlytools/cli 1.2.2 → 1.2.3
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/dist/assets/{arkade-_gS9wShF.js → arkade-B-ALutmU.js} +2 -2
- package/dist/assets/{arkade-_gS9wShF.js.map → arkade-B-ALutmU.js.map} +1 -1
- package/dist/assets/{hyperdhtRelay-CgYLaQz3.js → hyperdhtRelay-hKC9DpIH.js} +3 -3
- package/dist/assets/{hyperdhtRelay-CgYLaQz3.js.map → hyperdhtRelay-hKC9DpIH.js.map} +1 -1
- package/dist/assets/{mainline-1v8OTYvp.js → mainline-DriRu_hb.js} +2 -2
- package/dist/assets/{mainline-1v8OTYvp.js.map → mainline-DriRu_hb.js.map} +1 -1
- package/dist/assets/{server-Cy4ox7oD.js → server-DHMXMwNn.js} +867 -142
- package/dist/assets/server-DHMXMwNn.js.map +1 -0
- package/dist/assets/{src-XzcHzneo.js → src-_FzJkeAJ.js} +187 -44
- package/dist/assets/src-_FzJkeAJ.js.map +1 -0
- package/dist/assets/{usdt-Deo3g0Nq.js → usdt-D0lO2pRR.js} +2 -2
- package/dist/assets/{usdt-Deo3g0Nq.js.map → usdt-D0lO2pRR.js.map} +1 -1
- package/dist/ghostly.mjs +245 -82
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/assets/server-Cy4ox7oD.js.map +0 -1
- package/dist/assets/src-XzcHzneo.js.map +0 -1
package/dist/ghostly.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as __exportAll } from "./assets/rolldown-runtime-8H4AJuhK.js";
|
|
3
|
-
import { $ as
|
|
3
|
+
import { $n as readGroupHave, An as resolveLightningDestination, At as DidError, Ba as PR_CHECKS, Bo as extractParamsFromSdp, Bt as didWebHost, Ci as normalizeDomain, Cn as normalizePubkyKey, Ct as parseAtprotoDidDocument, Di as txtValue, Dn as homeserverWebEndpoint, Do as GhostlyHttpError, Dt as DID_DOCUMENT_MAX_BYTES$1, Ei as recordsName, En as pubkyProofPath, Et as verifyAtprotoRecordProof, Fo as RTC_CONFIG, Ft as didSignedMessages, Ga as cardLine, Gn as GROUP_FILE_LIMITS, Gt as signingKeys, Ha as RUN_RESULTS, Ho as signalHasVideo, Ht as parseDid, Ii as IdentityCheckUnavailable, Io as answersOffer, Ir as sanitizeTypingStatus, It as didWebDocumentUrl, Jn as GROUP_WANT_NO_FRAME, Ka as cardUrl, Kn as GROUP_HAVE_FRAME, Kt as staticDidDocument, La as replySnippet, Lo as buildSdpFromSignal, Lt as didWebFile, Mt as checkDidSignature, Nr as TYPING_KINDS, Nt as didGhostlyServiceEntry, Oi as wellKnownUrl, On as pubkyHomeserverOf, Ot as DID_FILE_MAX_BYTES, Po as CALL_SIGNAL_MAX_AGE_MS, Pr as TYPING_REFRESH_MS, Pt as didJwsAlg, Qi as irohRelayUrl, Qr as checkPushEndpoint, Qt as resolveDidDht, Ri as boundVerifiedIdentity, Ro as callRtcConfig, Rt as didWebFileNames, Si as nip05RootKey, Sn as newPubkyProofFolder, St as normalizeAtprotoHandle, Ti as recordsFromWellKnown, Tn as pubkyProofFile, Tt as AtprotoProofError, Ua as STATUS_CARD_LIMITS, Un as engineError, Ut as parseDidDocument, Va as PR_STATES, Vc as setLinkTraceSink, Vi as identityStatement, Vo as parseCallSignal, Vt as normalizeDid, Wa as buttonLabelClash, Wn as engineText, Wt as parseDidSignaturePaste, Xc as openRelayPayload, Xn as groupFileFallback, Yn as fetchesByItself, Zi as irohDescriptor, Zn as isGroupMessageId, _ as checkAppManifest, _i as dnsProofName, _t as atprotoDidMethod, aa as readImageMeta, ao as checkAppRevokeStatement, at as parseSshPublicKey, bi as encodeDnsQuery, bn as isPubkyKey, bt as checkAtprotoProofRecord, c as readAppStore, ca as sanitizeFileName, cl as toBase64Url, cn as WALLET_NETWORKS, ct as sshSignCommand, d as APP_BUNDLE_LIMITS, da as VOICE_LIMITS, dl as utf8Encode, do as signAppRevocation, dt as BitcoinAddressError, er as readGroupWant, fa as baseMime, fo as canonicalJson, ft as decodeAnyBitcoinAddress, g as assembleAppBundle, gi as decodeDnsResponse, gt as ATPROTO_PROOF_SCOPE, ha as voicePeaksOf, hi as DOMAIN_FILE_MAX_BYTES, ht as ATPROTO_PROOF_COLLECTION, il as sanitizeAvatar, io as appRef, it as SshSigError, jn as decodeBolt11, jo as LIMITS, jr as transferEnded, jt as checkDidJws, kn as requestLnurlInvoice, kr as ChatFiles, la as sanitizeMime, ll as toZ32, lo as readAppRevocations, lt as verifySshSignature, ma as parseVoiceMeta, mt as ATPROTO_FULL_SCOPE, n as appStoreDecision, no as APP_STATEMENT_LIMITS, ol as fromBase64Url$1, ot as parseSshSignature, pa as fileMessageText, pn as walletNetworkOf, po as canonicalJsonBytes, pt as ATPROTO_DECLARED_SCOPE, qa as checkStatusCard, qc as RELAY_PAYLOAD_MAX_BYTES, qt as strictBase64Url, ri as BUTTONS_CAPABILITY, rl as MAX_AVATAR_LENGTH, ro as appFingerprint, rt as SSHSIG_NAMESPACE, s as checkAppStoreIndex, sc as inviteLink, sl as randomBytes$1, st as sameSshKey, to as APP_PREFIXES, tr as readGroupWantNo, tt as groupName, u as signAppStore, ua as PLAYABLE_AUDIO, ul as utf8Decode, uo as signAppObject, ut as verifyBitcoinMessage, v as isAppUrl, vc as seedSigner, vi as domainTxtRecord, vt as atprotoProofRecord, wi as recordsFromTxt, wn as pubkyProofCapability, wt as shortAtprotoDid, xi as isPublicAddress, xn as isPubkyProofFolder, xt as isAtprotoDid, y as readAppBundle, ya as RateWindow, yi as domainWellKnownFile, yn as PUBKY_PROOF_MAX_BYTES, yt as atprotoProofRkey, za as ITEM_STATES, zo as callSignalHeardAt, zt as didWebFileUrl } from "./assets/src-_FzJkeAJ.js";
|
|
4
4
|
import { c as fileStore, f as store$1, i as STORES, m as wrap, o as databaseName, p as transact, u as openDb } from "./assets/idb-CTF6LF5B.js";
|
|
5
5
|
import { a as fileBytesOf, i as fileBytes, l as FILE_BYTES_STEP } from "./assets/fileBytes-BcYZhNgF.js";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { connect, createServer, isIP } from "node:net";
|
|
9
|
-
import { appendFileSync, chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openAsBlob, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
9
|
+
import { accessSync, appendFileSync, chmodSync, closeSync, constants, existsSync, fsyncSync, lstatSync, mkdirSync, openAsBlob, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
10
10
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
11
11
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
12
12
|
import { createBase58check } from "@scure/base";
|
|
13
13
|
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
14
14
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
15
|
-
import { access, constants, link, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
15
|
+
import { access, constants as constants$1, link, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
16
16
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
17
17
|
import { createHash, createPrivateKey, generateKeyPairSync, randomBytes, timingSafeEqual } from "node:crypto";
|
|
18
18
|
import { decode, npubEncode } from "nostr-tools/nip19";
|
|
@@ -35,7 +35,7 @@ import { IDBFactory, IDBKeyRange as IDBKeyRange$1, IDBTransaction } from "fake-i
|
|
|
35
35
|
import { format } from "node:util";
|
|
36
36
|
var package_default = {
|
|
37
37
|
name: "@ghostlytools/cli",
|
|
38
|
-
version: "1.2.
|
|
38
|
+
version: "1.2.3",
|
|
39
39
|
type: "module",
|
|
40
40
|
description: "Ghostly for bots and terminals: the app's own engine, headless on Node, behind a daemon and a `ghostly` command with JSON output.",
|
|
41
41
|
license: "MIT",
|
|
@@ -1323,7 +1323,7 @@ var GroupFileDesk = class {
|
|
|
1323
1323
|
}
|
|
1324
1324
|
/**
|
|
1325
1325
|
* An error when this group took as many announcements of mine as it takes in a minute, or as its members take from
|
|
1326
|
-
* one member in `announceWindowMs` (past that they would
|
|
1326
|
+
* one member in `announceWindowMs` (past that they would not fetch the file by themselves); null when one may go now.
|
|
1327
1327
|
*/
|
|
1328
1328
|
mayAnnounce(groupId) {
|
|
1329
1329
|
const wait = Math.max(...this.sentPaces(groupId).map((pace) => pace.wait()));
|
|
@@ -1340,9 +1340,9 @@ var GroupFileDesk = class {
|
|
|
1340
1340
|
this.own.delete(`${groupId}\n${meta.d}`);
|
|
1341
1341
|
}
|
|
1342
1342
|
/**
|
|
1343
|
-
* A message of the group announces a file (mine, or a member's): the file its row shows
|
|
1344
|
-
*
|
|
1345
|
-
*
|
|
1343
|
+
* A message of the group announces a file (mine, or a member's): the file its row shows. A member's is kept here
|
|
1344
|
+
* unfetched, and fetched now when it is one this device takes by itself and the member announced no more than the
|
|
1345
|
+
* group fetches that way. The same message again (a catch-up, a whole copy) is the same file.
|
|
1346
1346
|
*/
|
|
1347
1347
|
async announced(groupId, m, me) {
|
|
1348
1348
|
const known = this.byMessage.get(`${groupId}\n${m.id}`);
|
|
@@ -1384,9 +1384,9 @@ var GroupFileDesk = class {
|
|
|
1384
1384
|
this.queueHave(kept);
|
|
1385
1385
|
return messageFile(kept);
|
|
1386
1386
|
}
|
|
1387
|
-
|
|
1387
|
+
const paced = this.pace(this.takenPace, `${groupId}\n${m.sender}`, GROUP_FILE_LIMITS.announceTaken, GROUP_FILE_LIMITS.announceWindowMs).take();
|
|
1388
1388
|
const id = groupFileId(groupId, "in");
|
|
1389
|
-
const auto = this.deps.settings().autoDownloads && fetchesByItself(m.file, this.keptAutomatically(groupId));
|
|
1389
|
+
const auto = paced && this.deps.settings().autoDownloads && fetchesByItself(m.file, this.keptAutomatically(groupId));
|
|
1390
1390
|
const holders = this.ahead.get(`${groupId}\n${m.id}`) ?? [];
|
|
1391
1391
|
this.ahead.delete(`${groupId}\n${m.id}`);
|
|
1392
1392
|
const kept = {
|
|
@@ -2700,7 +2700,7 @@ var FILE_METHODS = {
|
|
|
2700
2700
|
throw new CliError("not_found", `No file ${path}`);
|
|
2701
2701
|
}
|
|
2702
2702
|
if (!info.isFile()) throw new CliError("bad_request", `${path} is not a file`);
|
|
2703
|
-
await access(path, constants.R_OK);
|
|
2703
|
+
await access(path, constants$1.R_OK);
|
|
2704
2704
|
if ("group" in target) return sendToGroup(ctx, params, target.group, path, info.size);
|
|
2705
2705
|
const { link } = target;
|
|
2706
2706
|
if (link.profile && link.dataLink === "open" && !link.capabilities?.files) throw new CliError("unavailable", "The contact's app takes no files");
|
|
@@ -6172,7 +6172,8 @@ async function verifyIdToken(token, options) {
|
|
|
6172
6172
|
}
|
|
6173
6173
|
//#endregion
|
|
6174
6174
|
//#region ../browser/src/proofs/oidc/flow.ts
|
|
6175
|
-
|
|
6175
|
+
/** Optional platform prefix (desktop `d.<port>.`, Android deep link `a.`), then 32 random bytes. */
|
|
6176
|
+
var STATE = /^(?:d\.\d{1,5}\.|a\.)?[A-Za-z0-9_-]{43}$/;
|
|
6176
6177
|
function scopeFor$1(provider, reveal) {
|
|
6177
6178
|
const scopes = provider.id === "apple" ? [] : ["openid"];
|
|
6178
6179
|
const claims = [];
|
|
@@ -8727,13 +8728,15 @@ var IDENTITY_METHODS = {
|
|
|
8727
8728
|
},
|
|
8728
8729
|
async "identity.cancel"(ctx, params) {
|
|
8729
8730
|
const draftId = str(params, "draft", true);
|
|
8730
|
-
drafts.delete(draftId);
|
|
8731
|
+
if (!drafts.delete(draftId)) throw new CliError("not_found", "No such draft here: it is kept by the daemon that made it, for an hour");
|
|
8731
8732
|
await node(ctx).cancelIdentityProof({ draftId });
|
|
8732
8733
|
return { cancelled: draftId };
|
|
8733
8734
|
},
|
|
8734
8735
|
async "identity.remove"(ctx, params) {
|
|
8735
|
-
|
|
8736
|
-
|
|
8736
|
+
const id = str(params, "id", true);
|
|
8737
|
+
if (!state(ctx).identityProofs.some((p) => p.id === id)) throw new CliError("not_found", `No identity proof ${JSON.stringify(id)} (ghostly identity list)`);
|
|
8738
|
+
await node(ctx).removeIdentityProof({ id });
|
|
8739
|
+
return { removed: id };
|
|
8737
8740
|
},
|
|
8738
8741
|
async "identity.share"(ctx, params) {
|
|
8739
8742
|
const link = chatOf(ctx, params);
|
|
@@ -8748,13 +8751,15 @@ var IDENTITY_METHODS = {
|
|
|
8748
8751
|
},
|
|
8749
8752
|
async "identity.withdraw"(ctx, params) {
|
|
8750
8753
|
const link = chatOf(ctx, params);
|
|
8754
|
+
const id = str(params, "id", true);
|
|
8755
|
+
if (!link.identities?.shared.some((s) => s.id === id)) throw new CliError("not_found", `No identity proof ${JSON.stringify(id)} shared in this chat (ghostly identity contact ${link.id})`);
|
|
8751
8756
|
await node(ctx).withdrawIdentityProof({
|
|
8752
8757
|
linkId: link.id,
|
|
8753
|
-
id
|
|
8758
|
+
id
|
|
8754
8759
|
});
|
|
8755
8760
|
return {
|
|
8756
8761
|
chat: link.id,
|
|
8757
|
-
withdrawn:
|
|
8762
|
+
withdrawn: id
|
|
8758
8763
|
};
|
|
8759
8764
|
},
|
|
8760
8765
|
/** What a contact shared in a chat, as this device checked it, and what this side shares there. */
|
|
@@ -8990,6 +8995,15 @@ async function proxy(request, response, chunks, send, cookies) {
|
|
|
8990
8995
|
plain(response, gone ? 503 : 502, gone ? "This service is not reachable. Services exist while their ghost is online." : `The request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
8991
8996
|
}
|
|
8992
8997
|
}
|
|
8998
|
+
/**
|
|
8999
|
+
* The service a command names, by its id: one of this profile's. The engine changes nothing for an id it does not
|
|
9000
|
+
* know and says nothing, so a mistyped id read as done ("shared": true, with a contact).
|
|
9001
|
+
*/
|
|
9002
|
+
function serviceOf(ctx, params) {
|
|
9003
|
+
const serviceId = str(params, "service", true);
|
|
9004
|
+
if (!state(ctx).services.some((s) => s.id === serviceId)) throw new CliError("not_found", `No service ${JSON.stringify(serviceId)} (ghostly service list)`);
|
|
9005
|
+
return serviceId;
|
|
9006
|
+
}
|
|
8993
9007
|
var SERVICE_METHODS = {
|
|
8994
9008
|
async "service.list"(ctx) {
|
|
8995
9009
|
return { services: state(ctx).services.map((s) => ({
|
|
@@ -9016,29 +9030,32 @@ var SERVICE_METHODS = {
|
|
|
9016
9030
|
});
|
|
9017
9031
|
},
|
|
9018
9032
|
async "service.remove"(ctx, params) {
|
|
9019
|
-
|
|
9020
|
-
|
|
9033
|
+
const serviceId = serviceOf(ctx, params);
|
|
9034
|
+
await node(ctx).removeService({ serviceId });
|
|
9035
|
+
return { removed: serviceId };
|
|
9021
9036
|
},
|
|
9022
9037
|
async "service.enable"(ctx, params) {
|
|
9038
|
+
const serviceId = serviceOf(ctx, params);
|
|
9023
9039
|
await node(ctx).setServiceEnabled({
|
|
9024
|
-
serviceId
|
|
9040
|
+
serviceId,
|
|
9025
9041
|
enabled: !bool(params, "off")
|
|
9026
9042
|
});
|
|
9027
9043
|
return {
|
|
9028
|
-
service:
|
|
9044
|
+
service: serviceId,
|
|
9029
9045
|
enabled: !bool(params, "off")
|
|
9030
9046
|
};
|
|
9031
9047
|
},
|
|
9032
9048
|
async "service.share"(ctx, params) {
|
|
9033
9049
|
const link = chatOf(ctx, params);
|
|
9034
9050
|
const shared = !bool(params, "off");
|
|
9051
|
+
const serviceId = serviceOf(ctx, params);
|
|
9035
9052
|
await node(ctx).setServiceShared({
|
|
9036
|
-
serviceId
|
|
9053
|
+
serviceId,
|
|
9037
9054
|
peerPubKeyZ32: link.peerPubKeyZ32,
|
|
9038
9055
|
shared
|
|
9039
9056
|
});
|
|
9040
9057
|
return {
|
|
9041
|
-
service:
|
|
9058
|
+
service: serviceId,
|
|
9042
9059
|
chat: link.id,
|
|
9043
9060
|
shared
|
|
9044
9061
|
};
|
|
@@ -13082,13 +13099,15 @@ var METHODS = {
|
|
|
13082
13099
|
},
|
|
13083
13100
|
async "chat.retry"(ctx, params) {
|
|
13084
13101
|
const link = chatOf(ctx, params);
|
|
13102
|
+
const messageId = str(params, "message", true);
|
|
13103
|
+
if (!await node(ctx).getMessage(link.id, messageId)) throw new CliError("not_found", `No message ${messageId} in this chat`);
|
|
13085
13104
|
await node(ctx).retryMessage({
|
|
13086
13105
|
linkId: link.id,
|
|
13087
|
-
messageId
|
|
13106
|
+
messageId
|
|
13088
13107
|
});
|
|
13089
13108
|
return {
|
|
13090
13109
|
chat: link.id,
|
|
13091
|
-
messageId
|
|
13110
|
+
messageId
|
|
13092
13111
|
};
|
|
13093
13112
|
},
|
|
13094
13113
|
async "chat.delete"(ctx, params) {
|
|
@@ -17350,6 +17369,14 @@ var refuseAnswers = Number(process.env.GHOSTLY_CALL_REFUSE_ANSWERS) || 0;
|
|
|
17350
17369
|
var GATHER_MS = 5e3;
|
|
17351
17370
|
var SETTLE_MS = 400;
|
|
17352
17371
|
/**
|
|
17372
|
+
* With a candidate in (a host one comes in milliseconds), the server reflexive one is waited for this long at most,
|
|
17373
|
+
* not the whole of `GATHER_MS`: a STUN server that answers does in tens of milliseconds, and one that never does (UDP
|
|
17374
|
+
* to it blocked, as on many office, school and hotel networks) made every call ring 5 s late and connect 5 s after
|
|
17375
|
+
* the answer. The apps' calls and the chats' data link bound theirs the same way (`HOST_GATHER_MS`,
|
|
17376
|
+
* `OFFER_HOST_GATHER_MS`).
|
|
17377
|
+
*/
|
|
17378
|
+
var HOST_GATHER_MS = 2e3;
|
|
17379
|
+
/**
|
|
17353
17380
|
* One call's peer connection. Its SDP never travels: the compact signal does (`local`), and each side rebuilds the
|
|
17354
17381
|
* other's SDP from it (`buildSdpFromSignal`), so the payload types, SSRC and mids here are the ones that rebuilt SDP
|
|
17355
17382
|
* uses: audio is mid 0, Opus is 111 unless the contact's offer says otherwise.
|
|
@@ -17421,7 +17448,7 @@ var CallMedia = class CallMedia {
|
|
|
17421
17448
|
const track = pc.addTrack(audioSection);
|
|
17422
17449
|
let videoTrack = null;
|
|
17423
17450
|
if (offer?.m?.includes("v")) {
|
|
17424
|
-
const video = new ndc.Video(String(offer.m.indexOf("v")), "
|
|
17451
|
+
const video = new ndc.Video(String(offer.m.indexOf("v")), "Inactive");
|
|
17425
17452
|
video.addVP8Codec(offer.vp ?? 96);
|
|
17426
17453
|
videoTrack = pc.addTrack(video);
|
|
17427
17454
|
videoTrack.onMessage(() => {});
|
|
@@ -17440,6 +17467,7 @@ var CallMedia = class CallMedia {
|
|
|
17440
17467
|
ss: [ssrc]
|
|
17441
17468
|
};
|
|
17442
17469
|
if (offer?.m) local.m = [...offer.m];
|
|
17470
|
+
if (videoTrack) local.vr = 0;
|
|
17443
17471
|
const listeners = [];
|
|
17444
17472
|
track.onMessage((packet) => {
|
|
17445
17473
|
for (const listener of listeners) listener(packet);
|
|
@@ -17520,20 +17548,26 @@ function mdnsNote(signal, log) {
|
|
|
17520
17548
|
lookup(host, { all: true }).then((found) => log(`${host} resolves here to ${found.map((a) => a.address).join(", ")}`), () => log(`${host} does not resolve here: the call connects only if the contact's checks reach this side`));
|
|
17521
17549
|
}
|
|
17522
17550
|
}
|
|
17523
|
-
/** Resolves once the local description has its candidates (see GATHER_MS). */
|
|
17551
|
+
/** Resolves once the local description has its candidates (see GATHER_MS and HOST_GATHER_MS). */
|
|
17524
17552
|
function gathering(pc) {
|
|
17525
17553
|
return new Promise((resolve) => {
|
|
17526
17554
|
let settle = null;
|
|
17555
|
+
let any = false;
|
|
17527
17556
|
const done = () => {
|
|
17528
17557
|
clearTimeout(cap);
|
|
17558
|
+
clearTimeout(bounded);
|
|
17529
17559
|
if (settle) clearTimeout(settle);
|
|
17530
17560
|
resolve();
|
|
17531
17561
|
};
|
|
17532
17562
|
const cap = setTimeout(done, GATHER_MS);
|
|
17563
|
+
const bounded = setTimeout(() => {
|
|
17564
|
+
if (any) done();
|
|
17565
|
+
}, HOST_GATHER_MS);
|
|
17533
17566
|
pc.onGatheringStateChange((state) => {
|
|
17534
17567
|
if (state === "complete") done();
|
|
17535
17568
|
});
|
|
17536
17569
|
pc.onLocalCandidate((candidate) => {
|
|
17570
|
+
any = true;
|
|
17537
17571
|
if (!settle && / typ srflx/.test(candidate)) settle = setTimeout(done, SETTLE_MS);
|
|
17538
17572
|
});
|
|
17539
17573
|
});
|
|
@@ -18189,23 +18223,15 @@ var EventHub = class {
|
|
|
18189
18223
|
request.onsuccess = () => resolve(request.result);
|
|
18190
18224
|
request.onerror = () => reject(request.error);
|
|
18191
18225
|
});
|
|
18192
|
-
const
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
request.onsuccess = () => {
|
|
18196
|
-
const cursor = request.result;
|
|
18197
|
-
if (!cursor) return resolve(out);
|
|
18198
|
-
out.push({
|
|
18199
|
-
key: cursor.key,
|
|
18200
|
-
value: cursor.value
|
|
18201
|
-
});
|
|
18202
|
-
cursor.continue();
|
|
18203
|
-
};
|
|
18226
|
+
const store = this.db.transaction(SEEN).objectStore(SEEN);
|
|
18227
|
+
const all = (request) => new Promise((resolve, reject) => {
|
|
18228
|
+
request.onsuccess = () => resolve(request.result);
|
|
18204
18229
|
request.onerror = () => reject(request.error);
|
|
18205
18230
|
});
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
|
|
18231
|
+
const [keys, values] = await Promise.all([all(store.getAllKeys()), all(store.getAll())]);
|
|
18232
|
+
for (let i = 0; i < keys.length; i++) {
|
|
18233
|
+
const [chat, id] = keys[i];
|
|
18234
|
+
this.seenOf(chat).set(id, values[i]);
|
|
18209
18235
|
}
|
|
18210
18236
|
this.mentionsNoted = this.firstRun || this.seen.get(FORMAT)?.get(MENTIONS_NOTED) === "yes";
|
|
18211
18237
|
}
|
|
@@ -18691,8 +18717,13 @@ var EventHub = class {
|
|
|
18691
18717
|
}
|
|
18692
18718
|
if (changes.length) this.persist(key, changes);
|
|
18693
18719
|
}
|
|
18720
|
+
/**
|
|
18721
|
+
* Relaxed: nobody waits for it, and a note lost to a power cut is made again from the history at the next start
|
|
18722
|
+
* (its event is then reported a second time, as after a crash between a message's row and its note). A disk sync
|
|
18723
|
+
* for each one held the whole daemon for milliseconds, three times per message sent.
|
|
18724
|
+
*/
|
|
18694
18725
|
persist(chat, changes) {
|
|
18695
|
-
const store = this.db.transaction(SEEN, "readwrite").objectStore(SEEN);
|
|
18726
|
+
const store = this.db.transaction(SEEN, "readwrite", { durability: "relaxed" }).objectStore(SEEN);
|
|
18696
18727
|
for (const [id, state] of changes) if (state === null) store.delete([chat, id]);
|
|
18697
18728
|
else store.put(state, [chat, id]);
|
|
18698
18729
|
}
|
|
@@ -19067,7 +19098,7 @@ function federationInfo(federationId, config) {
|
|
|
19067
19098
|
};
|
|
19068
19099
|
}
|
|
19069
19100
|
/** The first value a subscription gives that is final, or the last one within `waitMs`. */
|
|
19070
|
-
function watch(subscribe, final, waitMs) {
|
|
19101
|
+
function watch$1(subscribe, final, waitMs) {
|
|
19071
19102
|
return new Promise((resolve) => {
|
|
19072
19103
|
let last, done = false, cancel = () => {};
|
|
19073
19104
|
const finish = () => {
|
|
@@ -19138,13 +19169,13 @@ var RealClient = class {
|
|
|
19138
19169
|
return this.wallet.mint.tryCancelSpendNotes(operationId);
|
|
19139
19170
|
}
|
|
19140
19171
|
spendState(operationId, waitMs) {
|
|
19141
|
-
return watch((ok, fail) => this.wallet.mint.subscribeSpendNotes(operationId, (s) => ok(SPEND[s] ?? "created"), fail), spendFinal, waitMs);
|
|
19172
|
+
return watch$1((ok, fail) => this.wallet.mint.subscribeSpendNotes(operationId, (s) => ok(SPEND[s] ?? "created"), fail), spendFinal, waitMs);
|
|
19142
19173
|
}
|
|
19143
19174
|
redeem(notes, ghostly) {
|
|
19144
19175
|
return this.wallet.mint.reissueExternalNotes(notes, { ghostly });
|
|
19145
19176
|
}
|
|
19146
19177
|
redeemState(operationId, waitMs) {
|
|
19147
|
-
return watch((ok, fail) => this.wallet.mint.subscribeReissueExternalNotes(operationId, (s) => ok(typeof s === "string" ? REDEEM[s] ?? "created" : s && typeof s === "object" && "Failed" in s ? "failed" : "created"), (error) => {
|
|
19178
|
+
return watch$1((ok, fail) => this.wallet.mint.subscribeReissueExternalNotes(operationId, (s) => ok(typeof s === "string" ? REDEEM[s] ?? "created" : s && typeof s === "object" && "Failed" in s ? "failed" : "created"), (error) => {
|
|
19148
19179
|
ok("failed");
|
|
19149
19180
|
fail(error);
|
|
19150
19181
|
}), (s) => s === "done" || s === "failed", waitMs);
|
|
@@ -19157,7 +19188,7 @@ var RealClient = class {
|
|
|
19157
19188
|
};
|
|
19158
19189
|
}
|
|
19159
19190
|
receiveState(operationId, waitMs) {
|
|
19160
|
-
return watch((ok, fail) => this.wallet.lightning.subscribeLnReceive(operationId, (s) => ok(receiveOf(s)), fail), (s) => s === "claimed" || s === "canceled", waitMs);
|
|
19191
|
+
return watch$1((ok, fail) => this.wallet.lightning.subscribeLnReceive(operationId, (s) => ok(receiveOf(s)), fail), (s) => s === "claimed" || s === "canceled", waitMs);
|
|
19161
19192
|
}
|
|
19162
19193
|
async gatewayFee(amountMsats) {
|
|
19163
19194
|
await this.wallet.lightning.updateGatewayCache().catch(() => {});
|
|
@@ -19175,7 +19206,7 @@ var RealClient = class {
|
|
|
19175
19206
|
};
|
|
19176
19207
|
}
|
|
19177
19208
|
async payState(operationId, internal, waitMs) {
|
|
19178
|
-
const state = await watch((ok, fail) => internal ? this.wallet.lightning.subscribeInternalPayment(operationId, ok, fail) : this.wallet.lightning.subscribeLnPay(operationId, ok, fail), (s) => typeof s === "object" && !!s && ("success" in s || "preimage" in s || "refunded" in s || "refund_success" in s || "unexpected_error" in s || "funding_failed" in s) || s === "canceled", waitMs);
|
|
19209
|
+
const state = await watch$1((ok, fail) => internal ? this.wallet.lightning.subscribeInternalPayment(operationId, ok, fail) : this.wallet.lightning.subscribeLnPay(operationId, ok, fail), (s) => typeof s === "object" && !!s && ("success" in s || "preimage" in s || "refunded" in s || "refund_success" in s || "unexpected_error" in s || "funding_failed" in s) || s === "canceled", waitMs);
|
|
19179
19210
|
if (state && typeof state === "object") {
|
|
19180
19211
|
const s = state;
|
|
19181
19212
|
if (s.success?.preimage || s.preimage) return {
|
|
@@ -19510,6 +19541,10 @@ var SNAPSHOT = "snapshot.bin";
|
|
|
19510
19541
|
var JOURNAL = "journal.bin";
|
|
19511
19542
|
/** A journal past this is folded into the snapshot at the next quiet moment. */
|
|
19512
19543
|
var COMPACT_BYTES = 33554432;
|
|
19544
|
+
/** How long `close` waits for the transactions still running: one left open for good does not keep the process. */
|
|
19545
|
+
var CLOSE_WAIT_MS = 5e3;
|
|
19546
|
+
/** How long a relaxed transaction's entry may wait for a sync when no other transaction brings one. */
|
|
19547
|
+
var RELAXED_SYNC_MS = 1e3;
|
|
19513
19548
|
var BLOB = "__ghostlyBlob";
|
|
19514
19549
|
var RANGE = "__ghostlyRange";
|
|
19515
19550
|
var PROBE = "__ghostly_storage_probe__";
|
|
@@ -19532,12 +19567,14 @@ async function openPersistentIndexedDb(dir) {
|
|
|
19532
19567
|
let queue = Promise.resolve();
|
|
19533
19568
|
let queued = 0;
|
|
19534
19569
|
let schemaDirty = false;
|
|
19570
|
+
/** Set while the journal holds relaxed entries no sync has covered yet. */
|
|
19571
|
+
let relaxed = null;
|
|
19535
19572
|
const pending = /* @__PURE__ */ new WeakMap();
|
|
19536
19573
|
const snapshotPath = join(dir, SNAPSHOT);
|
|
19537
19574
|
const journalPath = join(dir, JOURNAL);
|
|
19538
19575
|
const snapshotFound = existsSync(snapshotPath), journalFound = existsSync(journalPath);
|
|
19539
|
-
if (snapshotFound) restore(classes, raw._databases, deserialize(readFileSync(snapshotPath)), (a, b) => factory.cmp(a, b));
|
|
19540
|
-
if (journalFound) replay(raw._databases, readFileSync(journalPath));
|
|
19576
|
+
if (snapshotFound) restore(classes, raw._databases, deserialize(readFileSync(snapshotPath)), keyOrder((a, b) => factory.cmp(a, b)));
|
|
19577
|
+
if (journalFound) replay(raw._databases, readFileSync(journalPath), keyOrder((a, b) => factory.cmp(a, b)));
|
|
19541
19578
|
restoring = false;
|
|
19542
19579
|
if (journalFound || !snapshotFound) await writeSnapshot();
|
|
19543
19580
|
const storeProto = classes.ObjectStore.prototype;
|
|
@@ -19584,7 +19621,7 @@ async function openPersistentIndexedDb(dir) {
|
|
|
19584
19621
|
if (this.mode === "versionchange") schemaDirty = true;
|
|
19585
19622
|
else {
|
|
19586
19623
|
const ops = pending.get(this._rollbackLog);
|
|
19587
|
-
if (ops?.length) append(ops);
|
|
19624
|
+
if (ops?.length) append(ops, this.durability === "relaxed");
|
|
19588
19625
|
}
|
|
19589
19626
|
pending.delete(this._rollbackLog);
|
|
19590
19627
|
if (schemaDirty) enqueue(writeSnapshot);
|
|
@@ -19606,34 +19643,54 @@ async function openPersistentIndexedDb(dir) {
|
|
|
19606
19643
|
});
|
|
19607
19644
|
return request;
|
|
19608
19645
|
};
|
|
19609
|
-
function append(ops) {
|
|
19646
|
+
function append(ops, lazy) {
|
|
19610
19647
|
if (queued === 0 && !ops.some((op) => op.kind === "put" && hasBlob(op.value))) {
|
|
19611
|
-
writeEntry(serialize(ops));
|
|
19648
|
+
writeEntry(serialize(ops), lazy);
|
|
19612
19649
|
return;
|
|
19613
19650
|
}
|
|
19614
19651
|
enqueue(async () => writeEntry(serialize(await Promise.all(ops.map(async (op) => op.kind === "put" ? {
|
|
19615
19652
|
...op,
|
|
19616
19653
|
value: await blobsToDisk(op.value)
|
|
19617
|
-
} : op)))));
|
|
19654
|
+
} : op))), lazy));
|
|
19618
19655
|
}
|
|
19619
19656
|
function enqueue(work) {
|
|
19620
19657
|
queued++;
|
|
19621
|
-
queue = queue.then(work).catch((
|
|
19622
|
-
process.stderr.write(`ghostly: storage write failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
19623
|
-
}).finally(() => {
|
|
19658
|
+
queue = queue.then(work).catch(failed).finally(() => {
|
|
19624
19659
|
queued--;
|
|
19625
19660
|
});
|
|
19626
19661
|
return queue;
|
|
19627
19662
|
}
|
|
19628
|
-
function
|
|
19663
|
+
function failed(error) {
|
|
19664
|
+
process.stderr.write(`ghostly: storage write failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
19665
|
+
}
|
|
19666
|
+
/**
|
|
19667
|
+
* `lazy`: a relaxed transaction's entry. A sync blocks the whole process for milliseconds, and what such a
|
|
19668
|
+
* transaction wrote can be made again (the event set's note of a message it already reported): written, it outlives
|
|
19669
|
+
* the process; a power cut may take its last second.
|
|
19670
|
+
*/
|
|
19671
|
+
function writeEntry(entry, lazy = false) {
|
|
19629
19672
|
if (!writing) return;
|
|
19630
19673
|
journalFd ??= openSync(journalPath, "a", 384);
|
|
19631
19674
|
const header = Buffer.alloc(4);
|
|
19632
19675
|
header.writeUInt32BE(entry.length);
|
|
19633
19676
|
writeSync(journalFd, Buffer.concat([header, entry]));
|
|
19634
|
-
|
|
19677
|
+
if (lazy) relaxed ??= setTimeout(() => {
|
|
19678
|
+
try {
|
|
19679
|
+
sync();
|
|
19680
|
+
} catch (error) {
|
|
19681
|
+
failed(error);
|
|
19682
|
+
}
|
|
19683
|
+
}, RELAXED_SYNC_MS).unref();
|
|
19684
|
+
else sync();
|
|
19635
19685
|
if (statSync(journalPath).size > COMPACT_BYTES) enqueue(writeSnapshot);
|
|
19636
19686
|
}
|
|
19687
|
+
function sync() {
|
|
19688
|
+
if (relaxed) {
|
|
19689
|
+
clearTimeout(relaxed);
|
|
19690
|
+
relaxed = null;
|
|
19691
|
+
}
|
|
19692
|
+
if (journalFd !== null) fsyncSync(journalFd);
|
|
19693
|
+
}
|
|
19637
19694
|
async function writeSnapshot() {
|
|
19638
19695
|
schemaDirty = false;
|
|
19639
19696
|
const snapshot = {
|
|
@@ -19676,12 +19733,29 @@ async function openPersistentIndexedDb(dir) {
|
|
|
19676
19733
|
closeSync(fd);
|
|
19677
19734
|
}
|
|
19678
19735
|
renameSync(temporary, snapshotPath);
|
|
19736
|
+
if (relaxed) {
|
|
19737
|
+
clearTimeout(relaxed);
|
|
19738
|
+
relaxed = null;
|
|
19739
|
+
}
|
|
19679
19740
|
if (journalFd !== null) {
|
|
19680
19741
|
closeSync(journalFd);
|
|
19681
19742
|
journalFd = null;
|
|
19682
19743
|
}
|
|
19683
19744
|
rmSync(journalPath, { force: true });
|
|
19684
19745
|
}
|
|
19746
|
+
/**
|
|
19747
|
+
* Resolves once no transaction is left to run. The engine does not wait for every write (`void db.deleteLink(…)`),
|
|
19748
|
+
* and fake-indexeddb runs a transaction on a later turn of the event loop: a command that closed in the turn it wrote
|
|
19749
|
+
* in (no chat to say goodbye to) took its last snapshot before the write, and answered done for what was not on disk.
|
|
19750
|
+
* Two quiet turns in a row: a write that begins when another ends (a chat's messages after its row) is seen too.
|
|
19751
|
+
*/
|
|
19752
|
+
async function settled() {
|
|
19753
|
+
const until = Date.now() + CLOSE_WAIT_MS;
|
|
19754
|
+
for (let quiet = 0; quiet < 2 && Date.now() < until;) {
|
|
19755
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
19756
|
+
quiet = [...raw._databases.values()].some((db) => db.transactions.some((transaction) => transaction._state !== "finished")) ? 0 : quiet + 1;
|
|
19757
|
+
}
|
|
19758
|
+
}
|
|
19685
19759
|
const installed = globalThis;
|
|
19686
19760
|
installed.indexedDB = factory;
|
|
19687
19761
|
installed.IDBKeyRange = IDBKeyRange$1;
|
|
@@ -19690,6 +19764,7 @@ async function openPersistentIndexedDb(dir) {
|
|
|
19690
19764
|
flush: () => queue,
|
|
19691
19765
|
compact: () => enqueue(writeSnapshot),
|
|
19692
19766
|
async close() {
|
|
19767
|
+
await settled();
|
|
19693
19768
|
await enqueue(writeSnapshot);
|
|
19694
19769
|
writing = false;
|
|
19695
19770
|
if (journalFd !== null) {
|
|
@@ -19781,17 +19856,49 @@ function dropIndexEntriesByRecord(classes, cmp) {
|
|
|
19781
19856
|
for (const candidate of gone.indexes.values()) if (candidate.records === this) index = candidate;
|
|
19782
19857
|
}
|
|
19783
19858
|
if (!gone || !index) return deleteByValue.call(this, key);
|
|
19784
|
-
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
|
|
19788
|
-
|
|
19859
|
+
return dropEntries(index, gone.records, cmp);
|
|
19860
|
+
};
|
|
19861
|
+
}
|
|
19862
|
+
/** Drops from an index the entries of records that left its store, and gives back the ones it dropped. */
|
|
19863
|
+
function dropEntries(index, records, cmp) {
|
|
19864
|
+
const dropped = [];
|
|
19865
|
+
for (const entry of indexEntries(index, records, cmp)) {
|
|
19866
|
+
if (index.records.keysAreUnique) {
|
|
19867
|
+
const held = index.records.get(entry.key);
|
|
19868
|
+
if (!held || cmp(held.value, entry.value) !== 0) continue;
|
|
19869
|
+
}
|
|
19870
|
+
index.records.records.delete(entry);
|
|
19871
|
+
dropped.push(entry);
|
|
19872
|
+
}
|
|
19873
|
+
return dropped;
|
|
19874
|
+
}
|
|
19875
|
+
/**
|
|
19876
|
+
* `cmp` for keys that are stored already (a record's, an index entry's): numbers, strings and arrays of them are
|
|
19877
|
+
* ordered here, as IndexedDB orders them (a number before a string before an array; strings by code unit; arrays item
|
|
19878
|
+
* by item, then the shorter first). Anything else (a date, bytes, a value that is no key) is left to `cmp`, which
|
|
19879
|
+
* checks and copies both keys at every call: on the way back from a snapshot that was a third of a large store's time.
|
|
19880
|
+
*/
|
|
19881
|
+
function keyOrder(cmp) {
|
|
19882
|
+
const order = (a, b) => {
|
|
19883
|
+
const first = typeof a, second = typeof b;
|
|
19884
|
+
if (first === "number" && second === "number") return a === b ? 0 : a > b ? 1 : a < b ? -1 : cmp(a, b);
|
|
19885
|
+
if (first === "string" && second === "string") return a === b ? 0 : a > b ? 1 : -1;
|
|
19886
|
+
if (first === "number" && second === "string") return -1;
|
|
19887
|
+
if (first === "string" && second === "number") return 1;
|
|
19888
|
+
if (Array.isArray(a)) {
|
|
19889
|
+
if (second === "number" || second === "string") return 1;
|
|
19890
|
+
if (!Array.isArray(b)) return cmp(a, b);
|
|
19891
|
+
const length = Math.min(a.length, b.length);
|
|
19892
|
+
for (let at = 0; at < length; at++) {
|
|
19893
|
+
const item = order(a[at], b[at]);
|
|
19894
|
+
if (item !== 0) return item;
|
|
19789
19895
|
}
|
|
19790
|
-
|
|
19791
|
-
dropped.push(entry);
|
|
19896
|
+
return a.length === b.length ? 0 : a.length > b.length ? 1 : -1;
|
|
19792
19897
|
}
|
|
19793
|
-
return
|
|
19898
|
+
if (Array.isArray(b) && (first === "number" || first === "string")) return -1;
|
|
19899
|
+
return cmp(a, b);
|
|
19794
19900
|
};
|
|
19901
|
+
return order;
|
|
19795
19902
|
}
|
|
19796
19903
|
/**
|
|
19797
19904
|
* The snapshot's records come in key order (as `writeSnapshot` walks them), so each store's tree and each index's are
|
|
@@ -19888,21 +19995,27 @@ function fill(store, sorted) {
|
|
|
19888
19995
|
/**
|
|
19889
19996
|
* Replays whole entries; a torn last entry (the process died mid-write) was never acknowledged and is dropped.
|
|
19890
19997
|
*
|
|
19891
|
-
* A store's indexes are set aside while its ops replay and
|
|
19998
|
+
* A store's indexes are set aside while its ops replay and brought in step once at the end: fake-indexeddb drops an
|
|
19892
19999
|
* overwritten or deleted record from an index by walking every record of it, so a crash after a busy chat took
|
|
19893
|
-
* tens of seconds to minutes to start, one write at a time against the whole profile.
|
|
20000
|
+
* tens of seconds to minutes to start, one write at a time against the whole profile. Only the rows the journal
|
|
20001
|
+
* touched change in an index (what each was before goes, what it is now comes in): a journal of one message costs one
|
|
20002
|
+
* message, not the profile's history once per index. A store the journal cleared has its indexes built again whole.
|
|
19894
20003
|
*/
|
|
19895
|
-
function replay(databases, journal) {
|
|
20004
|
+
function replay(databases, journal, cmp) {
|
|
19896
20005
|
const touched = /* @__PURE__ */ new Map();
|
|
19897
20006
|
try {
|
|
19898
20007
|
replayEntries(databases, journal, touched);
|
|
19899
20008
|
} finally {
|
|
19900
|
-
for (const [store, indexes] of touched) {
|
|
20009
|
+
for (const [store, { indexes, was, now }] of touched) {
|
|
20010
|
+
const records = store.records;
|
|
20011
|
+
delete records.put;
|
|
20012
|
+
delete records.delete;
|
|
19901
20013
|
store.rawIndexes = indexes;
|
|
19902
20014
|
for (const index of indexes.values()) {
|
|
19903
|
-
index.records.clear();
|
|
20015
|
+
if (!was) index.records.clear();
|
|
19904
20016
|
if (!index.initialized) continue;
|
|
19905
|
-
|
|
20017
|
+
if (was) dropEntries(index, [...was], cmp);
|
|
20018
|
+
for (const record of was ? now : store.records.values()) if (!absent(index.keyPath, record.value)) index.storeRecord(record);
|
|
19906
20019
|
}
|
|
19907
20020
|
}
|
|
19908
20021
|
}
|
|
@@ -19922,19 +20035,50 @@ function replayEntries(databases, journal, touched) {
|
|
|
19922
20035
|
for (const op of ops) {
|
|
19923
20036
|
const store = databases.get(op.db)?.rawObjectStores.get(op.store);
|
|
19924
20037
|
if (!store) continue;
|
|
19925
|
-
|
|
19926
|
-
|
|
19927
|
-
store.rawIndexes = /* @__PURE__ */ new Map();
|
|
19928
|
-
}
|
|
20038
|
+
let change = touched.get(store);
|
|
20039
|
+
if (!change) touched.set(store, change = watch(store));
|
|
19929
20040
|
if (op.kind === "put") store.storeRecord({
|
|
19930
20041
|
key: op.key,
|
|
19931
20042
|
value: blobsFromDisk(op.value)
|
|
19932
20043
|
}, false);
|
|
19933
20044
|
else if (op.kind === "delete") store.deleteRecord(keyFromDisk(op.key));
|
|
19934
|
-
else
|
|
20045
|
+
else {
|
|
20046
|
+
change.was = null;
|
|
20047
|
+
store.clear();
|
|
20048
|
+
}
|
|
19935
20049
|
}
|
|
19936
20050
|
}
|
|
19937
20051
|
}
|
|
20052
|
+
/**
|
|
20053
|
+
* Sets a store's indexes aside and notes, as its records are written over and removed, which ones left and which
|
|
20054
|
+
* ones came: the record store hands back what a put replaced and what a delete removed, so a row costs no lookup.
|
|
20055
|
+
* A record that came and left again was never in an index.
|
|
20056
|
+
*/
|
|
20057
|
+
function watch(store) {
|
|
20058
|
+
const change = {
|
|
20059
|
+
indexes: store.rawIndexes,
|
|
20060
|
+
was: store.rawIndexes.size ? /* @__PURE__ */ new Set() : null,
|
|
20061
|
+
now: /* @__PURE__ */ new Set()
|
|
20062
|
+
};
|
|
20063
|
+
store.rawIndexes = /* @__PURE__ */ new Map();
|
|
20064
|
+
if (!change.was) return change;
|
|
20065
|
+
const { put, delete: remove } = store.records;
|
|
20066
|
+
const left = (record) => {
|
|
20067
|
+
if (!change.now.delete(record)) change.was?.add(record);
|
|
20068
|
+
};
|
|
20069
|
+
store.records.put = (next, keep) => {
|
|
20070
|
+
const replaced = put.call(store.records, next, keep);
|
|
20071
|
+
if (replaced) left(replaced);
|
|
20072
|
+
change.now.add(next);
|
|
20073
|
+
return replaced;
|
|
20074
|
+
};
|
|
20075
|
+
store.records.delete = (key) => {
|
|
20076
|
+
const removed = remove.call(store.records, key);
|
|
20077
|
+
removed.forEach(left);
|
|
20078
|
+
return removed;
|
|
20079
|
+
};
|
|
20080
|
+
return change;
|
|
20081
|
+
}
|
|
19938
20082
|
function keyToDisk(key) {
|
|
19939
20083
|
if (key instanceof IDBKeyRange$1) return { [RANGE]: {
|
|
19940
20084
|
lower: key.lower,
|
|
@@ -20188,14 +20332,14 @@ async function startRuntime(paths, options = {}) {
|
|
|
20188
20332
|
(await import("./assets/ghostly_iroh_web-Dvzh3Y6E.js")).initSync({ module: irohWasmBytes() });
|
|
20189
20333
|
const [{ registerFileBytes }, { NodeFileBytes }] = await Promise.all([import("./assets/fileBytes-BcYZhNgF.js").then((n) => n.o), import("./assets/fileBytes-Dbifu5I5.js")]);
|
|
20190
20334
|
registerFileBytes("native", async () => new NodeFileBytes(paths.files), true);
|
|
20191
|
-
const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./assets/server-
|
|
20335
|
+
const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./assets/server-DHMXMwNn.js").then((n) => n.t), import("./assets/endpoint-pgFkR2AS.js")]);
|
|
20192
20336
|
const network = hyperdhtNetwork();
|
|
20193
20337
|
const dht = mainlineNetwork();
|
|
20194
|
-
const mainline = dht.off ? null : new (await (import("./assets/mainline-
|
|
20338
|
+
const mainline = dht.off ? null : new (await (import("./assets/mainline-DriRu_hb.js"))).Mainline({
|
|
20195
20339
|
bootstrap: dht.bootstrap,
|
|
20196
20340
|
host: dht.host
|
|
20197
20341
|
});
|
|
20198
|
-
const transport = mainline || pinned ? new (await (import("./assets/mainline-
|
|
20342
|
+
const transport = mainline || pinned ? new (await (import("./assets/mainline-DriRu_hb.js"))).RelaysAndDht(mainline, pinned ? { relays: pinned } : {}, !!pinned) : null;
|
|
20199
20343
|
const server = new EngineServer({
|
|
20200
20344
|
...transport ? { transport } : {},
|
|
20201
20345
|
irohWeb: true,
|
|
@@ -20340,10 +20484,14 @@ var guarded = false;
|
|
|
20340
20484
|
/**
|
|
20341
20485
|
* A daemon says an error nobody caught (with its stack, on stderr: the daemon's log when detached) and keeps serving:
|
|
20342
20486
|
* one request gone wrong must not take the profile offline. One-shot commands do not call this; they still fail loudly.
|
|
20487
|
+
*
|
|
20488
|
+
* `outputs`: what it writes to. A write there that fails (a pipe whose reader is gone: the process that started the
|
|
20489
|
+
* daemon died) is dropped, not an error nobody caught: saying it would be one more failed write, and the next, with
|
|
20490
|
+
* no turn left for anything else (a full core, and no stop on SIGTERM).
|
|
20343
20491
|
*/
|
|
20344
20492
|
function keepServing(proc = process, write = (text) => {
|
|
20345
20493
|
process.stderr.write(text);
|
|
20346
|
-
}) {
|
|
20494
|
+
}, outputs = proc === process ? [process.stdout, process.stderr] : []) {
|
|
20347
20495
|
if (proc === process) {
|
|
20348
20496
|
if (guarded) return;
|
|
20349
20497
|
guarded = true;
|
|
@@ -20353,6 +20501,7 @@ function keepServing(proc = process, write = (text) => {
|
|
|
20353
20501
|
write(`ghostly: ${what}, the daemon keeps serving: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
20354
20502
|
} catch {}
|
|
20355
20503
|
};
|
|
20504
|
+
for (const output of outputs) output.on("error", () => {});
|
|
20356
20505
|
proc.on("unhandledRejection", log("unhandled rejection"));
|
|
20357
20506
|
proc.on("uncaughtException", log("uncaught exception"));
|
|
20358
20507
|
}
|
|
@@ -20556,6 +20705,18 @@ function readCursor(path) {
|
|
|
20556
20705
|
const value = Number(readFileSync(path, "utf8").trim());
|
|
20557
20706
|
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
20558
20707
|
}
|
|
20708
|
+
/**
|
|
20709
|
+
* Whether a cursor can be kept at `path`, asked before anything starts: its folder is there and takes a file. Before, a
|
|
20710
|
+
* folder that was not there was found at the first event: the listener ran, said "event N failed: ENOENT" for each one
|
|
20711
|
+
* and never wrote the cursor, so the next start began at the journal's end and skipped what came while it was down.
|
|
20712
|
+
* Throws what the file system says (`asCliError` names the path and the code: not_found, refused).
|
|
20713
|
+
*/
|
|
20714
|
+
function checkCursor(path) {
|
|
20715
|
+
if (!path) return;
|
|
20716
|
+
const folder = dirname(resolve(path));
|
|
20717
|
+
if (!statSync(folder).isDirectory()) throw new CliError("not_found", `No such file or folder: ${folder}`);
|
|
20718
|
+
accessSync(folder, constants.W_OK | constants.X_OK);
|
|
20719
|
+
}
|
|
20559
20720
|
function writeCursor(path, seq) {
|
|
20560
20721
|
rmSync(path + ".tmp", { force: true });
|
|
20561
20722
|
writeFileSync(path + ".tmp", String(seq) + "\n", {
|
|
@@ -21361,6 +21522,7 @@ async function listenCommand(argv) {
|
|
|
21361
21522
|
description: ""
|
|
21362
21523
|
}
|
|
21363
21524
|
});
|
|
21525
|
+
noMoreThan(0, parsed.positionals, SPECIAL.find(([usage]) => usage.startsWith("listen "))[0]);
|
|
21364
21526
|
const g = globals(parsed);
|
|
21365
21527
|
pretty = false;
|
|
21366
21528
|
if (parsed.options.webhook) checkWebhook(String(parsed.options.webhook));
|
|
@@ -21369,6 +21531,7 @@ async function listenCommand(argv) {
|
|
|
21369
21531
|
if (turns && types.length) throw new CliError("usage", "--turns takes the place of --type: a turn is a message received, or a group message that mentions you");
|
|
21370
21532
|
const from = parsed.options.from ?? [], groups = parsed.options.group ?? [];
|
|
21371
21533
|
const cursor = parsed.options.cursor;
|
|
21534
|
+
checkCursor(cursor);
|
|
21372
21535
|
let since = parsed.options.since ?? readCursor(cursor);
|
|
21373
21536
|
let handle = null;
|
|
21374
21537
|
const start = async (call, peerOf) => {
|