@ghostlytools/cli 1.0.3 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
- package/dist/assets/{fileBytes-CpP9Vj7s.js → fileBytes-BQ84L9Fe.js} +2 -2
- package/dist/assets/{fileBytes-CpP9Vj7s.js.map → fileBytes-BQ84L9Fe.js.map} +1 -1
- package/dist/assets/{fileBytes-Br_MpeNT.js → fileBytes-DxiJHmTk.js} +3 -3
- package/dist/assets/fileBytes-DxiJHmTk.js.map +1 -0
- package/dist/assets/{fileBytesIdb-vubSXPoO.js → fileBytesIdb-DQkQ10PS.js} +3 -3
- package/dist/assets/{fileBytesIdb-vubSXPoO.js.map → fileBytesIdb-DQkQ10PS.js.map} +1 -1
- package/dist/assets/{fileBytesOpfs-6iSa6u2K.js → fileBytesOpfs-BFfR_68b.js} +3 -3
- package/dist/assets/{fileBytesOpfs-6iSa6u2K.js.map → fileBytesOpfs-BFfR_68b.js.map} +1 -1
- package/dist/assets/{ghostly_iroh_web-CPa_YEfb.js → ghostly_iroh_web-Dvzh3Y6E.js} +32 -32
- package/dist/assets/{ghostly_iroh_web-CPa_YEfb.js.map → ghostly_iroh_web-Dvzh3Y6E.js.map} +1 -1
- package/dist/assets/ghostly_iroh_web_bg.wasm +0 -0
- package/dist/assets/{hyperdhtRelay-VM9STKLd.js → hyperdhtRelay-CVLCuqVr.js} +3 -3
- package/dist/assets/{hyperdhtRelay-VM9STKLd.js.map → hyperdhtRelay-CVLCuqVr.js.map} +1 -1
- package/dist/assets/{idb-DIN_Miwb.js → idb-Co2lObpU.js} +4 -4
- package/dist/assets/idb-Co2lObpU.js.map +1 -0
- package/dist/assets/{mainline-CcO2f8jy.js → mainline-CKFpwXcB.js} +2 -2
- package/dist/assets/{mainline-CcO2f8jy.js.map → mainline-CKFpwXcB.js.map} +1 -1
- package/dist/assets/{restoredRows-tVxgyG23.js → restoredRows-C1O9FfA4.js} +38 -3
- package/dist/assets/restoredRows-C1O9FfA4.js.map +1 -0
- package/dist/assets/{server-BnNXYzR9.js → server-vGoe9MET.js} +9460 -784
- package/dist/assets/server-vGoe9MET.js.map +1 -0
- package/dist/assets/{src-7sdQpCoi.js → src-CJdPTl7Q.js} +4216 -1305
- package/dist/assets/src-CJdPTl7Q.js.map +1 -0
- package/dist/ghostly.mjs +374 -45
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/assets/fileBytes-Br_MpeNT.js.map +0 -1
- package/dist/assets/idb-DIN_Miwb.js.map +0 -1
- package/dist/assets/restoredRows-tVxgyG23.js.map +0 -1
- package/dist/assets/server-BnNXYzR9.js.map +0 -1
- package/dist/assets/src-7sdQpCoi.js.map +0 -1
package/dist/ghostly.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as fileBytesOf, d as __exportAll, i as fileBytes, l as FILE_BYTES_STEP } from "./assets/fileBytes-
|
|
3
|
-
import { $ as
|
|
4
|
-
import { d as wrap, l as store$1, n as STORES, o as fileStore, s as openDb, u as transact } from "./assets/idb-
|
|
2
|
+
import { a as fileBytesOf, d as __exportAll, i as fileBytes, l as FILE_BYTES_STEP } from "./assets/fileBytes-DxiJHmTk.js";
|
|
3
|
+
import { $ as checkAtprotoProofRecord, $s as randomBytes$1, $t as isPubkyKey, Aa as RTC_CONFIG, Ai as voicePeaksOf, B as sameSshKey, Ci as sanitizeFileName, Cn as resolveLightningDestination, Ct as parseDidSignaturePaste, Di as baseMime, Ea as LIMITS, Ei as VOICE_LIMITS, En as decodeBolt11, Et as strictBase64Url, Fa as extractParamsFromSdp, G as decodeAnyBitcoinAddress, H as verifySshSignature, Hs as openRelayPayload, I as SSHSIG_NAMESPACE, Ia as parseCallSignal, Ir as BUTTONS_CAPABILITY, J as ATPROTO_PROOF_COLLECTION, K as ATPROTO_DECLARED_SCOPE, Kt as walletNetworkOf, L as SshSigError, La as signalHasVideo, Ma as buildSdpFromSignal, Na as callRtcConfig, Ns as setLinkTraceSink, P as groupName, Pa as callSignalHeardAt, Pr as checkPushEndpoint, Q as atprotoProofRkey, Qi as replySnippet, Qr as IdentityCheckUnavailable, Qs as fromBase64Url$1, Qt as PUBKY_PROOF_MAX_BYTES, R as parseSshPublicKey, Sa as GhostlyHttpError, Sn as requestLnurlInvoice, St as parseDidDocument, Ti as PLAYABLE_AUDIO, Tn as engineText, Tt as staticDidDocument, U as verifyBitcoinMessage, V as sshSignCommand, Vt as WALLET_NETWORKS, W as BitcoinAddressError, X as atprotoDidMethod, Xs as sanitizeAvatar, Y as ATPROTO_PROOF_SCOPE, Ys as MAX_AVATAR_LENGTH, Z as atprotoProofRecord, _n as recordsFromTxt, _t as didWebFileNames, an as pubkyProofPath, at as verifyAtprotoRecordProof, bn as txtValue, bt as normalizeDid, cn as DOMAIN_FILE_MAX_BYTES, dn as domainTxtRecord, dt as checkDidSignature, ea as ITEM_STATES, ec as toBase64Url$1, ei as boundVerifiedIdentity, en as isPubkyProofFolder, et as isAtprotoDid, fn as domainWellKnownFile, ft as didGhostlyServiceEntry, gn as normalizeDomain, gr as TYPING_REFRESH_MS, gt as didWebFile, hn as nip05RootKey, hr as TYPING_KINDS, ht as didWebDocumentUrl, ia as checkStatusCard, in as pubkyProofFile, it as AtprotoProofError, ja as answersOffer, jt as resolveDidDht, ka as CALL_SIGNAL_MAX_AGE_MS, ki as parseVoiceMeta, ln as decodeDnsResponse, lt as DidError, mn as isPublicAddress, mt as didSignedMessages, na as STATUS_CARD_LIMITS, nc as utf8Decode, nn as normalizePubkyKey, ns as inviteLink, nt as parseAtprotoDidDocument, on as homeserverWebEndpoint, ot as DID_DOCUMENT_MAX_BYTES$1, pn as encodeDnsQuery, pt as didJwsAlg, q as ATPROTO_FULL_SCOPE, ra as buttonLabelClash, rc as utf8Encode, ri as identityStatement, rn as pubkyProofCapability, rt as shortAtprotoDid, sn as pubkyHomeserverOf, st as DID_FILE_MAX_BYTES, ta as RUN_RESULTS, tn as newPubkyProofFolder, tt as normalizeAtprotoHandle, un as dnsProofName, ut as checkDidJws, vn as recordsFromWellKnown, vr as sanitizeTypingStatus, vt as didWebFileUrl, wi as sanitizeMime, wn as engineError, wt as signingKeys, xi as readImageMeta, xn as wellKnownUrl, xt as parseDid, yn as recordsName, yt as didWebHost, z as parseSshSignature, zs as RELAY_PAYLOAD_MAX_BYTES } from "./assets/src-CJdPTl7Q.js";
|
|
4
|
+
import { d as wrap, i as databaseName, l as store$1, n as STORES, o as fileStore, s as openDb, u as transact } from "./assets/idb-Co2lObpU.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { connect, createServer, isIP } from "node:net";
|
|
8
8
|
import { appendFileSync, chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openAsBlob, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
9
9
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
10
|
-
import { createBase58check } from "@scure/base";
|
|
11
10
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
11
|
+
import { createBase58check } from "@scure/base";
|
|
12
12
|
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
13
13
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
14
14
|
import { link, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -22,7 +22,7 @@ import { createServer as createServer$1 } from "node:http";
|
|
|
22
22
|
import { Readable } from "node:stream";
|
|
23
23
|
import { pipeline } from "node:stream/promises";
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
|
-
import { generateMnemonic } from "@scure/bip39";
|
|
25
|
+
import { generateMnemonic, validateMnemonic } from "@scure/bip39";
|
|
26
26
|
import { createInterface } from "node:readline";
|
|
27
27
|
import { lookup } from "node:dns/promises";
|
|
28
28
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -34,7 +34,7 @@ import { IDBFactory, IDBKeyRange as IDBKeyRange$1, IDBTransaction } from "fake-i
|
|
|
34
34
|
import { format } from "node:util";
|
|
35
35
|
var package_default = {
|
|
36
36
|
name: "@ghostlytools/cli",
|
|
37
|
-
version: "1.
|
|
37
|
+
version: "1.1.1",
|
|
38
38
|
type: "module",
|
|
39
39
|
description: "Ghostly for bots and terminals: the app's own engine, headless on Node, behind a daemon and a `ghostly` command with JSON output.",
|
|
40
40
|
license: "MIT",
|
|
@@ -78,6 +78,7 @@ var package_default = {
|
|
|
78
78
|
"@scure/bip39": "2.4.0",
|
|
79
79
|
"@scure/btc-signer": "2.4.1",
|
|
80
80
|
"@secondts/bark": "0.25.0",
|
|
81
|
+
"@serenity-kit/opaque": "1.1.0",
|
|
81
82
|
"@synonymdev/pubky": "0.14.0",
|
|
82
83
|
"@tetherto/wdk-wallet-evm": "1.0.0-beta.19",
|
|
83
84
|
"b4a": "1.9.0",
|
|
@@ -644,6 +645,7 @@ var ENGINE_METHODS = [
|
|
|
644
645
|
"setWakeSubscription",
|
|
645
646
|
"setWakeMuted",
|
|
646
647
|
"wakeForCall",
|
|
648
|
+
"wakeConfirm",
|
|
647
649
|
"setFastPoll",
|
|
648
650
|
"createGroup",
|
|
649
651
|
"inviteToGroup",
|
|
@@ -666,7 +668,37 @@ var ENGINE_METHODS = [
|
|
|
666
668
|
"setGroupPicture",
|
|
667
669
|
"renameGroup",
|
|
668
670
|
"setGroupTyping",
|
|
669
|
-
"forgetGroup"
|
|
671
|
+
"forgetGroup",
|
|
672
|
+
"setGroupManage",
|
|
673
|
+
"deviceEnrollInvite",
|
|
674
|
+
"deviceEnrollConfirm",
|
|
675
|
+
"deviceEnrollCancel",
|
|
676
|
+
"deviceEnrollView",
|
|
677
|
+
"deviceEnrollJoin",
|
|
678
|
+
"deviceEnrollFinish",
|
|
679
|
+
"deviceEnrollRemove",
|
|
680
|
+
"deviceSet",
|
|
681
|
+
"devicePing",
|
|
682
|
+
"deviceHandoffVerifier",
|
|
683
|
+
"deviceHandoffPush",
|
|
684
|
+
"deviceHandoffPull",
|
|
685
|
+
"deviceHandoffAccept",
|
|
686
|
+
"deviceHandoffSettle",
|
|
687
|
+
"deviceHandoffCancel",
|
|
688
|
+
"deviceHandoffView",
|
|
689
|
+
"deviceHandoffAllow",
|
|
690
|
+
"deviceTakeoverInfo",
|
|
691
|
+
"deviceTakeover",
|
|
692
|
+
"deviceTurnPeek",
|
|
693
|
+
"deviceForkDiscard",
|
|
694
|
+
"deviceRestoreStartOwn",
|
|
695
|
+
"deviceRemove",
|
|
696
|
+
"deviceNewSecret",
|
|
697
|
+
"deviceSecretOfferDismiss",
|
|
698
|
+
"deviceSetNoticeSeen",
|
|
699
|
+
"deviceTurnCheck",
|
|
700
|
+
"devicePushState",
|
|
701
|
+
"devicePushSet"
|
|
670
702
|
];
|
|
671
703
|
/** Reads the hosts use outside `EngineApi`: the whole state, and one chat's stored messages. */
|
|
672
704
|
var ENGINE_READS = ["getState", "getMessages"];
|
|
@@ -1628,6 +1660,7 @@ var FILE_METHODS = {
|
|
|
1628
1660
|
});
|
|
1629
1661
|
const stored = await fileStore.get(fileId);
|
|
1630
1662
|
if (!stored?.metadata) throw new CliError("not_found", `No file ${fileId}`);
|
|
1663
|
+
if (stored.leftOut) throw new CliError("not_found", "Not in this backup: the light backup this profile was restored from left the file out");
|
|
1631
1664
|
const transfer = state(ctx).transfers[fileId] ?? stored.transfer;
|
|
1632
1665
|
if (transfer && transfer.state !== "done") throw new CliError("unavailable", `The file is not all here yet (${transfer.transferred} of ${transfer.size} bytes)`);
|
|
1633
1666
|
const target = str(params, "path") ? resolve(str(params, "path", true)) : join(resolve(str(params, "dir") ?? "."), sanitizeFileName(stored.metadata.name));
|
|
@@ -1643,7 +1676,8 @@ var FILE_METHODS = {
|
|
|
1643
1676
|
try {
|
|
1644
1677
|
const backend = stored.bytes ? await fileBytesOf(stored.bytes) : null;
|
|
1645
1678
|
if (backend) {
|
|
1646
|
-
const size = await backend.size(fileId)
|
|
1679
|
+
const size = await backend.size(fileId);
|
|
1680
|
+
if (size === null) throw new CliError("not_found", "The file's bytes are not on this device");
|
|
1647
1681
|
for (let offset = 0; offset < size; offset += FILE_BYTES_STEP) {
|
|
1648
1682
|
const chunk = await backend.read(fileId, offset, FILE_BYTES_STEP);
|
|
1649
1683
|
await out.write(chunk);
|
|
@@ -2992,7 +3026,7 @@ function nostrProofTemplate(statement) {
|
|
|
2992
3026
|
tags: [["d", "ghostly-identity-proof"], ["expiration", String(expiresAt)]]
|
|
2993
3027
|
};
|
|
2994
3028
|
}
|
|
2995
|
-
var hex$
|
|
3029
|
+
var hex$4 = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
2996
3030
|
var unhex$1 = (s) => Uint8Array.from(s.match(/../g), (x) => parseInt(x, 16));
|
|
2997
3031
|
function normalizeNostrKey(input) {
|
|
2998
3032
|
const value = input.trim();
|
|
@@ -3076,7 +3110,7 @@ var nostr = {
|
|
|
3076
3110
|
const expected = nostrProofTemplate(statement);
|
|
3077
3111
|
if (event.pubkey !== statement.binding.subject) throw new Error("Signed by another Nostr key");
|
|
3078
3112
|
if (event.kind !== expected.kind || event.created_at !== expected.created_at || event.content !== expected.content || JSON.stringify(event.tags) !== JSON.stringify(expected.tags)) throw new Error("The signer did not sign the exact Ghostly statement");
|
|
3079
|
-
const id = hex$
|
|
3113
|
+
const id = hex$4(sha256(utf8Encode(JSON.stringify([
|
|
3080
3114
|
0,
|
|
3081
3115
|
event.pubkey,
|
|
3082
3116
|
event.created_at,
|
|
@@ -6430,7 +6464,7 @@ async function approveAndWork(options, work, { grant, cookie, opened, settled, c
|
|
|
6430
6464
|
throw e;
|
|
6431
6465
|
}
|
|
6432
6466
|
const fail = (what) => (e) => {
|
|
6433
|
-
if (viaCookie && !cookieKept && refused(e)) throw new Error("Pubky Ring approved, but this browser
|
|
6467
|
+
if (viaCookie && !cookieKept && refused(e)) throw new Error("Pubky Ring approved, but this browser blocks your homeserver's sign-in cookie (Vanadium and Safari block cookies from other sites), so nothing was written. Approve with Pubky Passport instead, or use Firefox or the Ghostly desktop app.");
|
|
6434
6468
|
throw failure(what, e);
|
|
6435
6469
|
};
|
|
6436
6470
|
const approved = session;
|
|
@@ -6568,7 +6602,7 @@ var testIdentitiesEnabled = () => {
|
|
|
6568
6602
|
}
|
|
6569
6603
|
};
|
|
6570
6604
|
var HEX64 = /^[a-f0-9]{64}$/;
|
|
6571
|
-
var hex$
|
|
6605
|
+
var hex$3 = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
6572
6606
|
var unhex = (s) => Uint8Array.from(s.match(/../g), (x) => parseInt(x, 16));
|
|
6573
6607
|
var normalizeHexKey = (input) => {
|
|
6574
6608
|
const v = input.trim().toLowerCase();
|
|
@@ -6578,7 +6612,7 @@ var normalizeHexKey = (input) => {
|
|
|
6578
6612
|
var exactKeys = (raw, keys) => !!raw && typeof raw === "object" && !Array.isArray(raw) && Object.keys(raw).sort().join(",") === keys;
|
|
6579
6613
|
/** The key behind the fake in-app signer. Tests may replace it. */
|
|
6580
6614
|
var fakeKeyring = { seed: ed25519.utils.randomSecretKey() };
|
|
6581
|
-
var fakeKeySubject = (seed = fakeKeyring.seed) => hex$
|
|
6615
|
+
var fakeKeySubject = (seed = fakeKeyring.seed) => hex$3(ed25519.getPublicKey(seed));
|
|
6582
6616
|
var fakeKeySign = (statement, seed = fakeKeyring.seed) => ({ sig: toBase64Url$1(ed25519.sign(statement.bytes, seed)) });
|
|
6583
6617
|
var fakeKey = {
|
|
6584
6618
|
id: "fake-key",
|
|
@@ -7842,18 +7876,18 @@ var BackupWriter = class BackupWriter {
|
|
|
7842
7876
|
this.signal = signal;
|
|
7843
7877
|
}
|
|
7844
7878
|
/** `passphrase` null: a backup anyone can read (the caller has made sure that is what the person chose). */
|
|
7845
|
-
static async start(sink, passphrase, signal) {
|
|
7879
|
+
static async start(sink, passphrase, signal, version = 2) {
|
|
7846
7880
|
if (passphrase !== null && passphrase.length < 12) throw new Error("Use at least 12 characters for the backup passphrase");
|
|
7847
7881
|
check(signal);
|
|
7848
7882
|
const salt = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16)), base = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(7));
|
|
7849
7883
|
const header = passphrase === null ? {
|
|
7850
7884
|
format: "ghostly-backup",
|
|
7851
|
-
version
|
|
7885
|
+
version,
|
|
7852
7886
|
protection: "none",
|
|
7853
7887
|
check: { name: "SHA-256-chain" }
|
|
7854
7888
|
} : {
|
|
7855
7889
|
format: "ghostly-backup",
|
|
7856
|
-
version
|
|
7890
|
+
version,
|
|
7857
7891
|
protection: "passphrase",
|
|
7858
7892
|
kdf: {
|
|
7859
7893
|
name: "PBKDF2-SHA256",
|
|
@@ -7933,7 +7967,7 @@ async function readBackupHeader(source) {
|
|
|
7933
7967
|
return null;
|
|
7934
7968
|
}
|
|
7935
7969
|
if (header?.format !== "ghostly-backup" || typeof header.version !== "number" || header.version < 2) return null;
|
|
7936
|
-
if (header.version !== 2) throw new Error("This backup comes from a newer Ghostly; update to restore it");
|
|
7970
|
+
if (header.version !== 2 && header.version !== 3) throw new Error("This backup comes from a newer Ghostly; update to restore it");
|
|
7937
7971
|
if (header.protection === "none") {
|
|
7938
7972
|
if (header.check?.name !== "SHA-256-chain") throw new Error("Unsupported backup format");
|
|
7939
7973
|
} else if (header.protection === "passphrase") {
|
|
@@ -8046,6 +8080,21 @@ var BackupReader = class BackupReader {
|
|
|
8046
8080
|
}
|
|
8047
8081
|
};
|
|
8048
8082
|
//#endregion
|
|
8083
|
+
//#region ../browser/src/backup/light.ts
|
|
8084
|
+
/**
|
|
8085
|
+
* A light backup (WISP 05 § Light backups): everything of a profile but the bytes of its larger files. Their messages
|
|
8086
|
+
* and records stay; after a restore they show as not in this backup.
|
|
8087
|
+
*
|
|
8088
|
+
* The cut: a file of at most 1 MiB keeps its bytes (a short clip, a sticker, a screenshot, a document), and so does a
|
|
8089
|
+
* voice message of at most 4 MiB. Voice messages are part of the conversation, and the longest one the apps record
|
|
8090
|
+
* (15 minutes at 32 kbit/s, `VOICE_LIMITS`) is about 3.6 MB, so every recorded one stays. Photos from a camera
|
|
8091
|
+
* (usually 2 to 8 MB), videos and large files are what is left out.
|
|
8092
|
+
*/
|
|
8093
|
+
var LIGHT_FILE_BYTES = 1048576;
|
|
8094
|
+
var LIGHT_VOICE_BYTES = 4194304;
|
|
8095
|
+
/** Whether a file of this size keeps its bytes in a light backup. */
|
|
8096
|
+
var keptInLight = (size, voice) => size <= (voice ? LIGHT_VOICE_BYTES : LIGHT_FILE_BYTES);
|
|
8097
|
+
//#endregion
|
|
8049
8098
|
//#region src/profiles.ts
|
|
8050
8099
|
/**
|
|
8051
8100
|
* Profiles on disk (WISP 11xx § Profiles and files): `<home>/profiles/<name>/`, 0700, one engine store each.
|
|
@@ -8213,7 +8262,8 @@ var BACKUP_METHODS = {
|
|
|
8213
8262
|
/**
|
|
8214
8263
|
* Writes the backup to `path` (resolved by the command), sealed with `passphrase`, or not encrypted when
|
|
8215
8264
|
* `noPassphrase` is true: one of the two, never a guess. The store is folded first, so the copy is whole. It is
|
|
8216
|
-
* written beside `path` and only takes its name once it is complete: a backup that fails leaves no file.
|
|
8265
|
+
* written beside `path` and only takes its name once it is complete: a backup that fails leaves no file. `light`:
|
|
8266
|
+
* the files over the cut (`keptInLight`) are left out, their records kept.
|
|
8217
8267
|
*/
|
|
8218
8268
|
async "profile.backup"(ctx, params) {
|
|
8219
8269
|
const path = resolve(str(params, "path", true));
|
|
@@ -8224,11 +8274,16 @@ async "profile.backup"(ctx, params) {
|
|
|
8224
8274
|
await ctx.runtime.store.compact();
|
|
8225
8275
|
const paths = ctx.runtime.paths;
|
|
8226
8276
|
const store = join(paths.db, "snapshot.bin");
|
|
8227
|
-
const
|
|
8277
|
+
const light = bool(params, "light");
|
|
8278
|
+
const all = await Promise.all((await walk(paths.files)).map(async (name) => ({
|
|
8228
8279
|
name,
|
|
8229
8280
|
size: (await stat(join(paths.files, name))).size
|
|
8230
8281
|
})));
|
|
8282
|
+
const kept = async (file) => keptInLight(file.size, false) || file.size <= 4194304 && !!(await fileStore.get(basename(file.name)).catch(() => void 0))?.metadata?.voice;
|
|
8283
|
+
const files = [], leftOut = [];
|
|
8284
|
+
for (const file of all) (!light || await kept(file) ? files : leftOut).push(file);
|
|
8231
8285
|
const storeSize = (await stat(store)).size, bytes = files.reduce((sum, file) => sum + file.size, 0);
|
|
8286
|
+
const leftOutBytes = leftOut.reduce((sum, file) => sum + file.size, 0);
|
|
8232
8287
|
const partial = `${path}.partial-${process.pid}`;
|
|
8233
8288
|
let out = null;
|
|
8234
8289
|
try {
|
|
@@ -8248,7 +8303,14 @@ async "profile.backup"(ctx, params) {
|
|
|
8248
8303
|
format: FORMAT$1,
|
|
8249
8304
|
createdAt: Date.now(),
|
|
8250
8305
|
files: files.length,
|
|
8251
|
-
bytes
|
|
8306
|
+
bytes,
|
|
8307
|
+
...light && { light: {
|
|
8308
|
+
maxFileBytes: 1048576,
|
|
8309
|
+
maxVoiceBytes: 4194304,
|
|
8310
|
+
files: leftOut.length,
|
|
8311
|
+
bytes: leftOutBytes,
|
|
8312
|
+
ids: leftOut.map((file) => basename(file.name))
|
|
8313
|
+
} }
|
|
8252
8314
|
});
|
|
8253
8315
|
await put({
|
|
8254
8316
|
t: "file",
|
|
@@ -8283,7 +8345,12 @@ async "profile.backup"(ctx, params) {
|
|
|
8283
8345
|
path,
|
|
8284
8346
|
bytes: written,
|
|
8285
8347
|
files: files.length,
|
|
8286
|
-
protection: unsealed ? "none" : "passphrase"
|
|
8348
|
+
protection: unsealed ? "none" : "passphrase",
|
|
8349
|
+
...light && {
|
|
8350
|
+
light: true,
|
|
8351
|
+
leftOut: leftOut.length,
|
|
8352
|
+
leftOutBytes
|
|
8353
|
+
}
|
|
8287
8354
|
};
|
|
8288
8355
|
} finally {
|
|
8289
8356
|
await out?.close().catch(() => {});
|
|
@@ -8376,10 +8443,13 @@ async function restoringFolder(home, name) {
|
|
|
8376
8443
|
files: join(dir, "files")
|
|
8377
8444
|
};
|
|
8378
8445
|
}
|
|
8379
|
-
/**
|
|
8380
|
-
|
|
8446
|
+
/**
|
|
8447
|
+
* The folder is whole: marked as a restored copy (`RESTORED_MARK`, which names the files a light backup left out),
|
|
8448
|
+
* then given the profile's name. Never over a profile.
|
|
8449
|
+
*/
|
|
8450
|
+
async function finishRestore(home, name, work, leftOut = []) {
|
|
8381
8451
|
const paths = profilePaths(home, name);
|
|
8382
|
-
await writeFile(join(work.dir, RESTORED_MARK), "", { mode: 384 });
|
|
8452
|
+
await writeFile(join(work.dir, RESTORED_MARK), leftOut.length ? JSON.stringify({ leftOut }) : "", { mode: 384 });
|
|
8383
8453
|
if (existsSync(paths.dir)) throw new CliError("refused", `Profile ${name} exists: restore into a new name`);
|
|
8384
8454
|
await rename(work.dir, paths.dir);
|
|
8385
8455
|
return paths;
|
|
@@ -8424,6 +8494,7 @@ async function restoreProfile(home, name, file, passphrase) {
|
|
|
8424
8494
|
const first = await next();
|
|
8425
8495
|
const head = first?.json === void 0 ? null : parse(first.json);
|
|
8426
8496
|
if (head?.t !== "profile" || head.format !== FORMAT$1) throw new CliError("refused", "This backup is not a headless Ghostly profile");
|
|
8497
|
+
const leftOut = Array.isArray(head.light?.ids) ? head.light.ids.filter((id) => typeof id === "string") : [];
|
|
8427
8498
|
work = await restoringFolder(home, name);
|
|
8428
8499
|
const paths = work;
|
|
8429
8500
|
await mkdir(paths.db, {
|
|
@@ -8474,7 +8545,7 @@ async function restoreProfile(home, name, file, passphrase) {
|
|
|
8474
8545
|
} finally {
|
|
8475
8546
|
await out?.handle.close().catch(() => {});
|
|
8476
8547
|
}
|
|
8477
|
-
return await finishRestore(home, name, work);
|
|
8548
|
+
return await finishRestore(home, name, work, leftOut);
|
|
8478
8549
|
} catch (error) {
|
|
8479
8550
|
if (work) await rm(work.dir, {
|
|
8480
8551
|
recursive: true,
|
|
@@ -9000,6 +9071,45 @@ async function unsealSeed(seed, password) {
|
|
|
9000
9071
|
}
|
|
9001
9072
|
}
|
|
9002
9073
|
//#endregion
|
|
9074
|
+
//#region ../browser/src/engine/paymentAdapters/away.ts
|
|
9075
|
+
var away = /* @__PURE__ */ new Map();
|
|
9076
|
+
/** The wallets away from this device, by wallet id, with the name of their home device ("" when it has none here). */
|
|
9077
|
+
function setAwayWallets(next) {
|
|
9078
|
+
away = new Map(next);
|
|
9079
|
+
}
|
|
9080
|
+
/** The home device's name of a wallet away from this device, or undefined when it is here. */
|
|
9081
|
+
function awayFrom(id) {
|
|
9082
|
+
return away.get(id);
|
|
9083
|
+
}
|
|
9084
|
+
/** A wallet that cannot be used here: it is at home on another device. */
|
|
9085
|
+
var WalletAwayError = class extends Error {
|
|
9086
|
+
constructor(wallet, device) {
|
|
9087
|
+
super(device ? engineText("walletAway", {
|
|
9088
|
+
wallet,
|
|
9089
|
+
device
|
|
9090
|
+
}) : engineText("walletAwayUnnamed", { wallet }));
|
|
9091
|
+
this.name = "WalletAwayError";
|
|
9092
|
+
}
|
|
9093
|
+
};
|
|
9094
|
+
/**
|
|
9095
|
+
* Whether a single-writer wallet (Ark, Bark, Spark and the Breez source, Fedimint) may open its SDK now: the engine
|
|
9096
|
+
* says, from a good turn read under 60 seconds old (WISP 06 § Wallets). Every open asks, the retries of each wallet
|
|
9097
|
+
* included, so a device replaced while it was offline does not reopen one on a retry.
|
|
9098
|
+
*/
|
|
9099
|
+
var turnGate = async () => true;
|
|
9100
|
+
function setSingleWriterGate(gate) {
|
|
9101
|
+
turnGate = gate ?? (async () => true);
|
|
9102
|
+
}
|
|
9103
|
+
/** Throws (the wallet tries again later, as it does when its server does not answer) without a fresh good turn read. */
|
|
9104
|
+
async function requireTurn() {
|
|
9105
|
+
if (!await turnGate().catch(() => false)) throw new Error("Checking which device is active");
|
|
9106
|
+
}
|
|
9107
|
+
/** Throws when the wallet `id` is at home on another device. */
|
|
9108
|
+
function refuseAway(id, wallet) {
|
|
9109
|
+
const device = awayFrom(id);
|
|
9110
|
+
if (device !== void 0) throw new WalletAwayError(wallet, device);
|
|
9111
|
+
}
|
|
9112
|
+
//#endregion
|
|
9003
9113
|
//#region ../browser/src/engine/paymentAdapters/providers/sources.ts
|
|
9004
9114
|
/** Where a source is stored: the mode's one, or one of several (`key`). */
|
|
9005
9115
|
var sourceKey = (kind, mode, key) => `${kind}Source-${mode}${key ? `-${key}` : ""}`;
|
|
@@ -9117,6 +9227,13 @@ var ProviderSources = class {
|
|
|
9117
9227
|
await this.load(this.mode);
|
|
9118
9228
|
this.view = this.idle();
|
|
9119
9229
|
}
|
|
9230
|
+
/** One saved secret of the source (a handoff asks whether an LND card pins its certificate, and a Breez phrase), or undefined. */
|
|
9231
|
+
async secret(name) {
|
|
9232
|
+
const stored = this.stored;
|
|
9233
|
+
if (!stored?.secrets) return void 0;
|
|
9234
|
+
const value = JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? ""))[name];
|
|
9235
|
+
return typeof value === "string" && value ? value : void 0;
|
|
9236
|
+
}
|
|
9120
9237
|
/** The list of providers changed (a plugin registered): the picker is told, nothing reconnects. */
|
|
9121
9238
|
refreshOffered() {
|
|
9122
9239
|
this.view = {
|
|
@@ -9214,6 +9331,7 @@ var ProviderSources = class {
|
|
|
9214
9331
|
}
|
|
9215
9332
|
/** Creates a provider and checks it is on a network of this mode before anything uses it. */
|
|
9216
9333
|
async open(descriptor, settings, controller = this.controller) {
|
|
9334
|
+
if (descriptor.id === "breez") await requireTurn();
|
|
9217
9335
|
const signal = controller.signal;
|
|
9218
9336
|
const provider = await this.gate.within(abortable(descriptor.create(settings, {
|
|
9219
9337
|
...this.options.host(),
|
|
@@ -9384,7 +9502,7 @@ var ProviderSources = class {
|
|
|
9384
9502
|
s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key));
|
|
9385
9503
|
s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key));
|
|
9386
9504
|
});
|
|
9387
|
-
await this.disconnect();
|
|
9505
|
+
await this.disconnect(true);
|
|
9388
9506
|
this.stored = void 0;
|
|
9389
9507
|
this.seen = void 0;
|
|
9390
9508
|
this.failures = 0;
|
|
@@ -9407,7 +9525,7 @@ var ProviderSources = class {
|
|
|
9407
9525
|
});
|
|
9408
9526
|
this.stopped = true;
|
|
9409
9527
|
this.gate.close();
|
|
9410
|
-
await this.disconnect();
|
|
9528
|
+
await this.disconnect(true);
|
|
9411
9529
|
this.stored = void 0;
|
|
9412
9530
|
this.seen = void 0;
|
|
9413
9531
|
this.view = {
|
|
@@ -9448,13 +9566,24 @@ var ProviderSources = class {
|
|
|
9448
9566
|
async refresh() {
|
|
9449
9567
|
clearTimeout(this.timer);
|
|
9450
9568
|
const provider = this.provider;
|
|
9451
|
-
if (!provider
|
|
9569
|
+
if (!provider) return;
|
|
9570
|
+
if (!this.options.refresh) {
|
|
9571
|
+
if (!this.view.read) {
|
|
9572
|
+
this.view = {
|
|
9573
|
+
...this.view,
|
|
9574
|
+
read: true
|
|
9575
|
+
};
|
|
9576
|
+
this.options.changed();
|
|
9577
|
+
}
|
|
9578
|
+
return;
|
|
9579
|
+
}
|
|
9452
9580
|
try {
|
|
9453
9581
|
const details = await this.options.refresh(provider);
|
|
9454
9582
|
if (provider !== this.provider) return;
|
|
9455
9583
|
this.view = {
|
|
9456
9584
|
...this.view,
|
|
9457
9585
|
...details,
|
|
9586
|
+
read: true,
|
|
9458
9587
|
balanceAt: void 0,
|
|
9459
9588
|
error: void 0
|
|
9460
9589
|
};
|
|
@@ -9486,7 +9615,8 @@ var ProviderSources = class {
|
|
|
9486
9615
|
s[STORES.settings].put(seen, key);
|
|
9487
9616
|
}).catch(() => {});
|
|
9488
9617
|
}
|
|
9489
|
-
|
|
9618
|
+
/** `forget`: the source is removed, so what it keeps on this device goes too (a provider's own `forget`). */
|
|
9619
|
+
async disconnect(forget = false) {
|
|
9490
9620
|
clearTimeout(this.timer);
|
|
9491
9621
|
clearTimeout(this.retry);
|
|
9492
9622
|
this.controller.abort();
|
|
@@ -9495,7 +9625,7 @@ var ProviderSources = class {
|
|
|
9495
9625
|
this.provider = void 0;
|
|
9496
9626
|
this.descriptor = void 0;
|
|
9497
9627
|
this.secrets = {};
|
|
9498
|
-
await provider?.close()
|
|
9628
|
+
await (forget && provider?.forget ? provider.forget() : provider?.close())?.catch(() => {});
|
|
9499
9629
|
}
|
|
9500
9630
|
/** Shutting down: nothing reconnects afterwards. */
|
|
9501
9631
|
async stop() {
|
|
@@ -9510,6 +9640,9 @@ var PREFIX = "lightningOp-";
|
|
|
9510
9640
|
var opKey = (direction, hash) => `${PREFIX}${direction}-${hash}`;
|
|
9511
9641
|
var RANGE$1 = () => IDBKeyRange.bound(PREFIX, `${PREFIX}\uffff`);
|
|
9512
9642
|
/** Every Lightning operation journaled, of every network and card. */
|
|
9643
|
+
/** Lightning payments whose call to the source has not returned, on any card of this page (WISP 06 § Payments in flight). */
|
|
9644
|
+
var paying = 0;
|
|
9645
|
+
var lightningPaying = () => paying;
|
|
9513
9646
|
async function readLightningJournal() {
|
|
9514
9647
|
return wrap((await store$1(STORES.settings, "readonly")).getAll(RANGE$1()));
|
|
9515
9648
|
}
|
|
@@ -9748,6 +9881,7 @@ var LightningService = class {
|
|
|
9748
9881
|
ref: quote.provider.paymentRef?.(quote.invoice)
|
|
9749
9882
|
};
|
|
9750
9883
|
await this.put(op);
|
|
9884
|
+
paying += 1;
|
|
9751
9885
|
try {
|
|
9752
9886
|
const result = await quote.provider.payInvoice(quote.invoice, quote.maxFee, context.note);
|
|
9753
9887
|
op = result.state === "paid" ? {
|
|
@@ -9775,6 +9909,8 @@ var LightningService = class {
|
|
|
9775
9909
|
state: "unknown",
|
|
9776
9910
|
error: quote.provider.capabilities.lookup ? "No answer from the source. It is being checked; nothing is paid again." : "No answer from the source, and it cannot be asked: check this payment in the wallet itself. It is never paid again."
|
|
9777
9911
|
};
|
|
9912
|
+
} finally {
|
|
9913
|
+
paying -= 1;
|
|
9778
9914
|
}
|
|
9779
9915
|
await this.put(op);
|
|
9780
9916
|
if (op.state !== "paid") this.schedule(OUT_POLL_MS);
|
|
@@ -9939,6 +10075,156 @@ var LightningService = class {
|
|
|
9939
10075
|
}
|
|
9940
10076
|
};
|
|
9941
10077
|
//#endregion
|
|
10078
|
+
//#region ../browser/src/engine/paymentAdapters/providers/breezDatabases.ts
|
|
10079
|
+
var netOf = (network) => network === "bitcoin" || network === "mainnet" ? "mainnet" : "regtest";
|
|
10080
|
+
var hex$2 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
10081
|
+
var digest = (text) => hex$2(sha256(new TextEncoder().encode(text))).slice(0, 16);
|
|
10082
|
+
/** What a Breez database name looks like, old or new: nothing about the seed or the profile shows in it. */
|
|
10083
|
+
var BREEZ_DATABASE = /^ghostly-breez-(mainnet|regtest)-[0-9a-f]{16}$/;
|
|
10084
|
+
/** The name every profile of the origin used for this phrase before databases were per profile. */
|
|
10085
|
+
var breezLegacyName = (network, mnemonic) => `ghostly-breez-${netOf(network)}-${digest(`ghostly-breez:${netOf(network)}:${mnemonic}`)}`;
|
|
10086
|
+
/** The name of this phrase's Breez database in one profile (`space`: the profile's peer database, WISP 04). */
|
|
10087
|
+
var breezDatabaseName = (network, mnemonic, space = databaseName()) => `ghostly-breez-${netOf(network)}-${digest(`ghostly-breez:${space}:${netOf(network)}:${mnemonic}`)}`;
|
|
10088
|
+
/**
|
|
10089
|
+
* The databases the SDK keeps under one storage name. SDK 0.26 roots them there: `<name>/<network>/<identity>` and
|
|
10090
|
+
* `<name>/<network>/<identity>-tree`, the identity read from the seed inside the SDK. Older builds kept `<name>`
|
|
10091
|
+
* itself. A browser without `indexedDB.databases()` cannot list them: only the bare names are known there.
|
|
10092
|
+
*/
|
|
10093
|
+
async function breezDatabaseFiles(name) {
|
|
10094
|
+
const files = [name, `${name}-tree`];
|
|
10095
|
+
if (typeof indexedDB.databases !== "function") return files;
|
|
10096
|
+
const under = (await indexedDB.databases()).map((d) => d.name ?? "").filter((file) => file.startsWith(`${name}/`));
|
|
10097
|
+
return [.../* @__PURE__ */ new Set([...files, ...under])];
|
|
10098
|
+
}
|
|
10099
|
+
var REGISTER = "ghostly-breez";
|
|
10100
|
+
var STORE = "databases";
|
|
10101
|
+
var request$1 = (r) => new Promise((resolve, reject) => {
|
|
10102
|
+
r.onsuccess = () => resolve(r.result);
|
|
10103
|
+
r.onerror = () => reject(r.error);
|
|
10104
|
+
});
|
|
10105
|
+
function openRegister() {
|
|
10106
|
+
return new Promise((resolve, reject) => {
|
|
10107
|
+
const open = indexedDB.open(REGISTER, 1);
|
|
10108
|
+
open.onupgradeneeded = () => {
|
|
10109
|
+
if (!open.result.objectStoreNames.contains(STORE)) open.result.createObjectStore(STORE);
|
|
10110
|
+
};
|
|
10111
|
+
open.onsuccess = () => resolve(open.result);
|
|
10112
|
+
open.onerror = () => reject(open.error);
|
|
10113
|
+
open.onblocked = () => reject(/* @__PURE__ */ new Error("The Breez register is blocked"));
|
|
10114
|
+
});
|
|
10115
|
+
}
|
|
10116
|
+
async function withRegister(mode, run) {
|
|
10117
|
+
const db = await openRegister();
|
|
10118
|
+
try {
|
|
10119
|
+
const tx = db.transaction(STORE, mode);
|
|
10120
|
+
const done = new Promise((resolve, reject) => {
|
|
10121
|
+
tx.oncomplete = () => resolve();
|
|
10122
|
+
tx.onerror = tx.onabort = () => reject(tx.error);
|
|
10123
|
+
});
|
|
10124
|
+
const result = await run(tx.objectStore(STORE));
|
|
10125
|
+
await done;
|
|
10126
|
+
return result;
|
|
10127
|
+
} finally {
|
|
10128
|
+
db.close();
|
|
10129
|
+
}
|
|
10130
|
+
}
|
|
10131
|
+
/**
|
|
10132
|
+
* Whether the SDK keeps a wallet under this storage name on this device (`breezDatabaseFiles`). A browser that cannot
|
|
10133
|
+
* list databases is answered yes: the first profile then takes the old name whether or not it is there, which is
|
|
10134
|
+
* safe, since only one profile ever has it.
|
|
10135
|
+
*/
|
|
10136
|
+
async function exists(name) {
|
|
10137
|
+
if (typeof indexedDB.databases !== "function") return true;
|
|
10138
|
+
return (await indexedDB.databases()).some((d) => d.name === name || !!d.name?.startsWith(`${name}/`));
|
|
10139
|
+
}
|
|
10140
|
+
/**
|
|
10141
|
+
* The Breez database this profile opens for this phrase: the old shared one when this profile owns it (or is the
|
|
10142
|
+
* first to claim it), else the profile's own. Noted in the register either way. A device whose IndexedDB cannot be
|
|
10143
|
+
* read gets the profile's own name: never a database another profile may use.
|
|
10144
|
+
*/
|
|
10145
|
+
async function breezDatabase(network, mnemonic, space = databaseName()) {
|
|
10146
|
+
const fresh = breezDatabaseName(network, mnemonic, space);
|
|
10147
|
+
if (typeof indexedDB === "undefined") return fresh;
|
|
10148
|
+
const legacy = breezLegacyName(network, mnemonic);
|
|
10149
|
+
try {
|
|
10150
|
+
const legacyHere = await exists(legacy);
|
|
10151
|
+
return await withRegister("readwrite", async (store) => {
|
|
10152
|
+
const [owner, own] = await Promise.all([request$1(store.get(legacy)), request$1(store.get(fresh))]);
|
|
10153
|
+
if (owner?.space === space) return legacy;
|
|
10154
|
+
if (!own && !owner && legacyHere) {
|
|
10155
|
+
store.put({
|
|
10156
|
+
name: legacy,
|
|
10157
|
+
space,
|
|
10158
|
+
network: netOf(network),
|
|
10159
|
+
legacy: true
|
|
10160
|
+
}, legacy);
|
|
10161
|
+
return legacy;
|
|
10162
|
+
}
|
|
10163
|
+
if (!own) store.put({
|
|
10164
|
+
name: fresh,
|
|
10165
|
+
space,
|
|
10166
|
+
network: netOf(network)
|
|
10167
|
+
}, fresh);
|
|
10168
|
+
return fresh;
|
|
10169
|
+
});
|
|
10170
|
+
} catch {
|
|
10171
|
+
return fresh;
|
|
10172
|
+
}
|
|
10173
|
+
}
|
|
10174
|
+
/** The names this profile has noted for this phrase (nothing is claimed): what removing the wallet deletes. */
|
|
10175
|
+
async function breezDatabasesFor(network, mnemonic, space = databaseName()) {
|
|
10176
|
+
const names = [breezLegacyName(network, mnemonic), breezDatabaseName(network, mnemonic, space)];
|
|
10177
|
+
return (await breezDatabasesOf(space)).filter((name) => names.includes(name));
|
|
10178
|
+
}
|
|
10179
|
+
/**
|
|
10180
|
+
* The name this profile uses for this phrase, read without claiming anything: the one it noted (`breezDatabase`), else
|
|
10181
|
+
* the profile's own name. What a handoff deletes and compares (WISP 06 § Wallets): never the old shared database of
|
|
10182
|
+
* another profile that has not opened the phrase since the change.
|
|
10183
|
+
*/
|
|
10184
|
+
async function breezDatabaseInUse(network, mnemonic, space = databaseName()) {
|
|
10185
|
+
return (await breezDatabasesFor(network, mnemonic, space))[0] ?? breezDatabaseName(network, mnemonic, space);
|
|
10186
|
+
}
|
|
10187
|
+
/** Every Breez database name noted for a profile of this device. */
|
|
10188
|
+
async function breezDatabasesOf(space) {
|
|
10189
|
+
if (typeof indexedDB === "undefined") return [];
|
|
10190
|
+
try {
|
|
10191
|
+
return (await withRegister("readonly", (store) => request$1(store.getAll()))).filter((e) => e?.space === space && BREEZ_DATABASE.test(e.name)).map((e) => e.name);
|
|
10192
|
+
} catch {
|
|
10193
|
+
return [];
|
|
10194
|
+
}
|
|
10195
|
+
}
|
|
10196
|
+
var deleteDatabase = (name) => new Promise((resolve) => {
|
|
10197
|
+
try {
|
|
10198
|
+
const r = indexedDB.deleteDatabase(name);
|
|
10199
|
+
r.onsuccess = r.onerror = r.onblocked = () => resolve();
|
|
10200
|
+
} catch {
|
|
10201
|
+
resolve();
|
|
10202
|
+
}
|
|
10203
|
+
});
|
|
10204
|
+
var deleteStrictly = (name) => new Promise((resolve, reject) => {
|
|
10205
|
+
const r = indexedDB.deleteDatabase(name);
|
|
10206
|
+
r.onsuccess = () => resolve();
|
|
10207
|
+
r.onerror = () => reject(r.error ?? /* @__PURE__ */ new Error(`${name} was not deleted`));
|
|
10208
|
+
r.onblocked = () => reject(/* @__PURE__ */ new Error(`${name} is still open`));
|
|
10209
|
+
});
|
|
10210
|
+
/**
|
|
10211
|
+
* Deletes one Breez wallet's databases (all the SDK keeps under the name) and its note in the register. Anything else
|
|
10212
|
+
* is refused. `strict` (a handoff, WISP 06 § Wallets): a delete that is blocked or fails rejects and the note stays,
|
|
10213
|
+
* so the caller tries again.
|
|
10214
|
+
*/
|
|
10215
|
+
async function dropBreezDatabase(name, options = {}) {
|
|
10216
|
+
if (!BREEZ_DATABASE.test(name) || typeof indexedDB === "undefined") return;
|
|
10217
|
+
for (const file of await breezDatabaseFiles(name).catch(() => [name, `${name}-tree`])) await (options.strict ? deleteStrictly(file) : deleteDatabase(file));
|
|
10218
|
+
await withRegister("readwrite", (store) => {
|
|
10219
|
+
store.delete(name);
|
|
10220
|
+
}).catch(() => {});
|
|
10221
|
+
}
|
|
10222
|
+
//#endregion
|
|
10223
|
+
//#region ../browser/src/engine/paymentAdapters/providers/recoveryPhrase.ts
|
|
10224
|
+
/** A phrase as BIP 39 wants it, however it was typed or pasted. */
|
|
10225
|
+
var normalizePhrase = (text = "") => text.trim().toLowerCase().split(/\s+/).join(" ");
|
|
10226
|
+
var isRecoveryPhrase = (text) => validateMnemonic(normalizePhrase(text), wordlist);
|
|
10227
|
+
//#endregion
|
|
9942
10228
|
//#region ../browser/src/engine/paymentAdapters/providers/lightningCards.ts
|
|
9943
10229
|
/** The card of Lightning through the Cashu mints: at most one per network, shown while the network has mints. */
|
|
9944
10230
|
var CASHU_CARD = "cashu";
|
|
@@ -10275,14 +10561,37 @@ var LightningCards = class {
|
|
|
10275
10561
|
list() {
|
|
10276
10562
|
return readLightningJournal();
|
|
10277
10563
|
}
|
|
10564
|
+
/**
|
|
10565
|
+
* What a handoff needs to know of the cards (WISP 06 § Wallets), from their sealed settings: the LND cards that pin a
|
|
10566
|
+
* certificate, and each Breez card's database on this device, by wallet id.
|
|
10567
|
+
*/
|
|
10568
|
+
async handoffFacts() {
|
|
10569
|
+
const pinned = [], breez = /* @__PURE__ */ new Map();
|
|
10570
|
+
for (const [id, service] of this.services) {
|
|
10571
|
+
const wallet = `lightning:${this.network}:${id}`, providerId = service.view.providerId;
|
|
10572
|
+
if (providerId === "lnd" && await service.sources.secret("certificate")) pinned.push(wallet);
|
|
10573
|
+
if (providerId === "breez") {
|
|
10574
|
+
const mnemonic = await service.sources.secret("mnemonic");
|
|
10575
|
+
if (mnemonic) breez.set(wallet, await breezDatabaseInUse(this.network === "mainnet" ? "bitcoin" : "regtest", normalizePhrase(mnemonic)));
|
|
10576
|
+
}
|
|
10577
|
+
}
|
|
10578
|
+
return {
|
|
10579
|
+
pinned,
|
|
10580
|
+
breez
|
|
10581
|
+
};
|
|
10582
|
+
}
|
|
10583
|
+
/** The cards whose source is opened on this device: not one at home on another device (WISP 06 § Wallets that stay home). */
|
|
10584
|
+
here() {
|
|
10585
|
+
return [...this.services].filter(([id]) => awayFrom(`lightning:${this.network}:${id}`) === void 0).map(([, s]) => s);
|
|
10586
|
+
}
|
|
10278
10587
|
async reconcile() {
|
|
10279
|
-
await Promise.all(this.
|
|
10588
|
+
await Promise.all(this.here().map((s) => s.reconcile()));
|
|
10280
10589
|
}
|
|
10281
10590
|
async recover() {
|
|
10282
|
-
for (const s of this.
|
|
10591
|
+
for (const s of this.here()) await s.recover();
|
|
10283
10592
|
}
|
|
10284
10593
|
async ensureReady() {
|
|
10285
|
-
await Promise.all(this.
|
|
10594
|
+
await Promise.all(this.here().map((s) => s.ensureReady()));
|
|
10286
10595
|
}
|
|
10287
10596
|
refreshOffered() {
|
|
10288
10597
|
for (const s of this.all()) s.refreshOffered();
|
|
@@ -16682,6 +16991,15 @@ function irohWasmBytes() {
|
|
|
16682
16991
|
* settings and the Mainline DHT directly (read when the relays fail, written always).
|
|
16683
16992
|
* The caller holds the profile's lock.
|
|
16684
16993
|
*/
|
|
16994
|
+
/** The files a light backup left out, as the restore wrote them in the mark (an empty mark: none). */
|
|
16995
|
+
function leftOutIn(mark) {
|
|
16996
|
+
try {
|
|
16997
|
+
const ids = JSON.parse(readFileSync(mark, "utf8") || "{}").leftOut;
|
|
16998
|
+
return Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
|
|
16999
|
+
} catch {
|
|
17000
|
+
return [];
|
|
17001
|
+
}
|
|
17002
|
+
}
|
|
16685
17003
|
async function startRuntime(paths, options = {}) {
|
|
16686
17004
|
const pinned = pkarrRelays();
|
|
16687
17005
|
const trace = process.env.GHOSTLY_LINK_TRACE;
|
|
@@ -16690,7 +17008,9 @@ async function startRuntime(paths, options = {}) {
|
|
|
16690
17008
|
const restored = join(paths.dir, RESTORED_MARK);
|
|
16691
17009
|
if (existsSync(restored)) {
|
|
16692
17010
|
try {
|
|
16693
|
-
|
|
17011
|
+
const rows = await import("./assets/restoredRows-C1O9FfA4.js");
|
|
17012
|
+
await rows.markRestoredWallet();
|
|
17013
|
+
await rows.markLeftOutFiles(leftOutIn(restored));
|
|
16694
17014
|
await store.compact();
|
|
16695
17015
|
} catch (error) {
|
|
16696
17016
|
await store.close().catch(() => {});
|
|
@@ -16706,20 +17026,21 @@ async function startRuntime(paths, options = {}) {
|
|
|
16706
17026
|
if (testMints) (await Promise.resolve().then(() => mints_exports)).declareTestMints(testMints.split(","));
|
|
16707
17027
|
await installEventSource();
|
|
16708
17028
|
await preloadWalletModules();
|
|
16709
|
-
(await import("./assets/ghostly_iroh_web-
|
|
16710
|
-
const [{ registerFileBytes }, { NodeFileBytes }] = await Promise.all([import("./assets/fileBytes-
|
|
17029
|
+
(await import("./assets/ghostly_iroh_web-Dvzh3Y6E.js")).initSync({ module: irohWasmBytes() });
|
|
17030
|
+
const [{ registerFileBytes }, { NodeFileBytes }] = await Promise.all([import("./assets/fileBytes-DxiJHmTk.js").then((n) => n.o), import("./assets/fileBytes-BQ84L9Fe.js")]);
|
|
16711
17031
|
registerFileBytes("native", async () => new NodeFileBytes(paths.files), true);
|
|
16712
|
-
const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./assets/server-
|
|
17032
|
+
const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./assets/server-vGoe9MET.js").then((n) => n.t), import("./assets/endpoint-CaSbSFDw.js")]);
|
|
16713
17033
|
const network = hyperdhtNetwork();
|
|
16714
17034
|
const dht = mainlineNetwork();
|
|
16715
|
-
const mainline = dht.off ? null : new (await (import("./assets/mainline-
|
|
17035
|
+
const mainline = dht.off ? null : new (await (import("./assets/mainline-CKFpwXcB.js"))).Mainline({
|
|
16716
17036
|
bootstrap: dht.bootstrap,
|
|
16717
17037
|
host: dht.host
|
|
16718
17038
|
});
|
|
16719
|
-
const transport = mainline || pinned ? new (await (import("./assets/mainline-
|
|
17039
|
+
const transport = mainline || pinned ? new (await (import("./assets/mainline-CKFpwXcB.js"))).RelaysAndDht(mainline, pinned ? { relays: pinned } : {}, !!pinned) : null;
|
|
16720
17040
|
const server = new EngineServer({
|
|
16721
17041
|
...transport ? { transport } : {},
|
|
16722
17042
|
irohWeb: true,
|
|
17043
|
+
singleDevice: true,
|
|
16723
17044
|
nativeTransports: { "hyperdht/1": (seedB64) => createHyperEndpoint(fromBase64Url$1(seedB64), network) },
|
|
16724
17045
|
automaticWallets: false,
|
|
16725
17046
|
fedimintSdk: nodeFedimintSdk(join(paths.dir, "fedimint")),
|
|
@@ -17296,12 +17617,13 @@ var SPECIAL = [
|
|
|
17296
17617
|
}
|
|
17297
17618
|
],
|
|
17298
17619
|
[
|
|
17299
|
-
"profile backup --out <file> [--passphrase-file f | --no-passphrase]",
|
|
17620
|
+
"profile backup --out <file> [--passphrase-file f | --no-passphrase] [--light]",
|
|
17300
17621
|
"A backup of the profile, encrypted with a passphrase (from a file or GHOSTLY_BACKUP_PASSPHRASE); --no-passphrase makes one anyone can read",
|
|
17301
17622
|
{
|
|
17302
17623
|
out: o("string", "The backup file to write"),
|
|
17303
17624
|
"passphrase-file": o("string", "The passphrase, from this file (else GHOSTLY_BACKUP_PASSPHRASE)"),
|
|
17304
|
-
passphrase: o("boolean", "--no-passphrase: do not encrypt. The file then holds the profile's keys and wallet secrets in the clear")
|
|
17625
|
+
passphrase: o("boolean", "--no-passphrase: do not encrypt. The file then holds the profile's keys and wallet secrets in the clear"),
|
|
17626
|
+
light: o("boolean", "Leave out files over 1 MB (voice messages over 4 MB); their messages stay")
|
|
17305
17627
|
}
|
|
17306
17628
|
],
|
|
17307
17629
|
[
|
|
@@ -17475,6 +17797,10 @@ async function profileCommand(sub, argv) {
|
|
|
17475
17797
|
passphrase: {
|
|
17476
17798
|
type: "boolean",
|
|
17477
17799
|
description: ""
|
|
17800
|
+
},
|
|
17801
|
+
light: {
|
|
17802
|
+
type: "boolean",
|
|
17803
|
+
description: ""
|
|
17478
17804
|
}
|
|
17479
17805
|
},
|
|
17480
17806
|
restore: {
|
|
@@ -17552,16 +17878,19 @@ async function profileCommand(sub, argv) {
|
|
|
17552
17878
|
}
|
|
17553
17879
|
case "backup": {
|
|
17554
17880
|
const out = parsed.options.out;
|
|
17555
|
-
if (typeof out !== "string") throw new CliError("usage", "ghostly profile backup --out <file> [--passphrase-file f | --no-passphrase] (or GHOSTLY_BACKUP_PASSPHRASE)");
|
|
17881
|
+
if (typeof out !== "string") throw new CliError("usage", "ghostly profile backup --out <file> [--passphrase-file f | --no-passphrase] [--light] (or GHOSTLY_BACKUP_PASSPHRASE)");
|
|
17556
17882
|
const unsealed = parsed.options.passphrase === false;
|
|
17557
17883
|
if (parsed.options.passphrase === true) throw new CliError("usage", "The passphrase comes from --passphrase-file or GHOSTLY_BACKUP_PASSPHRASE, never the command line");
|
|
17558
17884
|
if (unsealed && (parsed.options["passphrase-file"] !== void 0 || process.env.GHOSTLY_BACKUP_PASSPHRASE)) throw new CliError("usage", "A passphrase is set (--passphrase-file or GHOSTLY_BACKUP_PASSPHRASE) and --no-passphrase was given: choose one");
|
|
17885
|
+
const light = parsed.options.light === true ? { light: true } : {};
|
|
17559
17886
|
const made = await withSession(g, (s) => s.call("profile.backup", unsealed ? {
|
|
17560
17887
|
path: resolve(out),
|
|
17561
|
-
noPassphrase: true
|
|
17888
|
+
noPassphrase: true,
|
|
17889
|
+
...light
|
|
17562
17890
|
} : {
|
|
17563
17891
|
path: resolve(out),
|
|
17564
|
-
passphrase: backupPassphrase(parsed.options["passphrase-file"])
|
|
17892
|
+
passphrase: backupPassphrase(parsed.options["passphrase-file"]),
|
|
17893
|
+
...light
|
|
17565
17894
|
}));
|
|
17566
17895
|
if (unsealed) process.stderr.write("Not encrypted: this file holds the profile's keys, chats and wallet secrets in the clear. Anyone who gets it gets everything in it, including any money in its wallets.\n");
|
|
17567
17896
|
print(made);
|
|
@@ -18279,6 +18608,6 @@ async function run(argv) {
|
|
|
18279
18608
|
//#region src/bin.ts
|
|
18280
18609
|
run(process.argv.slice(2));
|
|
18281
18610
|
//#endregion
|
|
18282
|
-
export {
|
|
18611
|
+
export { onAdaptersChanged as $, intentRepository as A, MESSAGE_DETAILS_MAX_BYTES as B, ProviderSources as C, FileAppender as Ct, requireTurn as D, streamStored as Dt, refuseAway as E, storedSize as Et, ModeGate as F, fakesLightning as G, BITCOIN_INVOICE_ON_TESTNET as H, WrongNetworkError as I, mintNetwork as J, isTestMint as K, networkLabel as L, sealSeed as M, unsealSeed as N, setAwayWallets as O, ModeChanged as P, identityProviders as Q, CASHU_MINT_SOURCE as R, lightningPaying as S, paymentWireId as St, awayFrom as T, removeStored as Tt, DEFAULT_MINTS as U, WALLET_TYPES as V, TEST_MINT as W, BACKUP_MEDIA_TYPE as X, paysItsOwnInvoices as Y, MAX_BACKUP_BYTES as Z, normalizePhrase as _, pairedWireReply as _t, createFailure as a, SourceConfigError as at, breezDatabasesFor as b, replyTo as bt, paymentNetwork as c, networkMode as ct, networkReason as d, resolveAtprotoDid as dt, registeredLightningProviders as et, rpcHost as f, setOwnDidSource as ft, isRecoveryPhrase as g, groupReply as gt, cardsKey as h, chosenResolver as ht, assertConfirmedReal as i, PROVIDER_PLATFORMS as it, newDeviceKey as j, setSingleWriterGate as k, paymentNetworksOf as l, redact as lt, LightningCards as m, verifyIdentity as mt, loadFedimintSdk as n, reserveAdapterIds as nt, createTiming as o, SourceUnreachableError as ot, CASHU_CARD as p, boundedIdentityFetch as pt, isWorthlessMint as q, WALLET_NAMES as r, NothingSpentError as rt, crossNetwork as s, isNothingSpentError as st, fedimintDatabase as t, registeredOnchainProviders as tt, walletInstances as u, assertPublicServer as ut, breezDatabase as v, receivedPairedReply as vt, sourceKey as w, readStored as wt, dropBreezDatabase as x, paymentAlias as xt, breezDatabaseInUse as y, replyRef as yt, cashuMint as z };
|
|
18283
18612
|
|
|
18284
18613
|
//# sourceMappingURL=ghostly.mjs.map
|