@ghostlytools/cli 1.2.1 → 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/README.md CHANGED
@@ -102,7 +102,7 @@ The rows are in alphabetical order of their first command, and every command of
102
102
  | `chat accept <chat> <method> [--off] [--networks mainnet,testnet]` | Which ways of paying the chat takes |
103
103
  | `chat connect <chat>`, `chat disconnect <chat> [--hold <minutes>]` | Reconnect now (ends a hold); close the live session, or with `--hold` stay off the direct link that long ([Staying off the direct link](#staying-off-the-direct-link)) |
104
104
  | `chat history <chat> [--limit n] [--before x] [--after x]` | Messages, oldest first; `x` is a message id or a time in ms |
105
- | `chat list`, `chat show <chat>` | Chats, and one chat's connection: transports, last attempt, comparison code |
105
+ | `chat list [--show-secret]`, `chat show <chat> [--show-secret]` | Chats, and one chat's connection: transports, last attempt, comparison code. A pending chat's `invite` prints as `<hidden>` without `--show-secret` |
106
106
  | `chat pay <chat> <sats> [--memo t] [--network n] [--confirm-real]` | Ecash to a contact |
107
107
  | `chat rename <chat> <name>`, `chat remove <chat> --yes` | Name it here; delete it (keys and history) here |
108
108
  | `chat request <chat> <sats> [--memo t] [--method m] [--rail r]`, `chat pay-request <chat> <payment>` | Ask a contact to pay; pay the contact's request |
@@ -260,7 +260,9 @@ Nothing prints a seed, a key, a wallet phrase or ecash unless asked with `--show
260
260
  credentials, and engine calls that answer with secrets (`exportLinks`, `walletExport`, the wallets' backups) are
261
261
  refused without it. A group's entry link lets anyone who reads it join: `group show`, `group list`, the admin
262
262
  commands, `group.created` events and `engine getState` print it as `<hidden>`; `--show-secret`, `group create` and
263
- `group link` print it.
263
+ `group link` print it. A pending chat's invite code lets whoever reads it take the chat: every command that prints a
264
+ chat (`chat list`, `chat show`, `chat rename`, …) and `engine getState` print it as `<hidden>`; `invite create` and
265
+ `chat list` or `chat show` with `--show-secret` print it.
264
266
 
265
267
  ## Events
266
268
 
package/SKILL.md CHANGED
@@ -360,7 +360,8 @@ proof needs a person who holds the key: leave that to the owner.
360
360
  4 (timed out) or 5 (needs `--force`, `--yes` or `--confirm-real`).
361
361
  - `send` refuses text that looks like a recovery phrase, a private key or a Cashu token unless `--force`.
362
362
  - Spending real money (Mainnet) needs `--confirm-real`.
363
- - Seeds, keys and backups are never printed without `--show-secret`; `group show` and `group list` hide entry links.
363
+ - Seeds, keys and backups are never printed without `--show-secret`; `group show` and `group list` hide entry links, and
364
+ `chat list` and `chat show` a pending chat's invite code.
364
365
 
365
366
  ## Anything else
366
367
 
@@ -1,4 +1,4 @@
1
- import { cn as validatePaymentTarget, on as assertWholeSats } from "./src-aKGjb_yz.js";
1
+ import { fn as validatePaymentTarget, un as assertWholeSats } from "./src-_FzJkeAJ.js";
2
2
  import { ArkAddress, EsploraProvider, IndexedDBContractRepository, IndexedDBWalletRepository, MnemonicIdentity, RestArkProvider, RestIndexerProvider, Transaction, Wallet, assertAllowedSighashTypes, assertSubmittedArkTxid, canRecoverOnchain, isRecoverable, matchServerCheckpoints, verifyTapscriptSignatures } from "@arkade-os/sdk";
3
3
  import { tapLeafHash } from "@scure/btc-signer/payment.js";
4
4
  //#region ../browser/src/engine/paymentAdapters/arkade.ts
@@ -359,4 +359,4 @@ var ArkadeAdapter = class ArkadeAdapter {
359
359
  //#endregion
360
360
  export { ARK_NETWORKS, ArkadeAdapter };
361
361
 
362
- //# sourceMappingURL=arkade-CtCpFaF7.js.map
362
+ //# sourceMappingURL=arkade-B-ALutmU.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"arkade-CtCpFaF7.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/arkade.ts"],"sourcesContent":["import {\n Wallet, MnemonicIdentity, RestArkProvider, RestIndexerProvider, EsploraProvider,\n IndexedDBWalletRepository, IndexedDBContractRepository, ArkAddress, Transaction,\n assertSubmittedArkTxid, matchServerCheckpoints, assertAllowedSighashTypes, verifyTapscriptSignatures,\n isRecoverable, canRecoverOnchain, type StorageConfig,\n} from \"@arkade-os/sdk\";\nimport { tapLeafHash } from \"@scure/btc-signer/payment.js\";\nimport { assertWholeSats, validatePaymentTarget, type PaymentAdapter, type PaymentReview, type PaymentTarget } from \"@ghostly/core\";\n\nexport interface ArkConfig { network: \"bitcoin\" | \"regtest\" | \"signet\" | \"mutinynet\"; provider: string; explorer: string; serverKey: string; walletId: string }\nexport interface ArkPrepared { signedTx: string; checkpoints: string[]; txid: string; address: string; amount: number; fee: number; finalCheckpoints?: string[] }\nconst unbase64 = (s: string) => Uint8Array.from(atob(s),c=>c.charCodeAt(0));\nconst base64 = (bytes: Uint8Array) => btoa(Array.from(bytes,b=>String.fromCharCode(b)).join(\"\"));\nconst hex = (bytes: Uint8Array) => Array.from(bytes,b=>b.toString(16).padStart(2,\"0\")).join(\"\");\nclass PreparedOnly extends Error {}\nexport const ARK_NETWORKS: ArkConfig[\"network\"][] = [\"bitcoin\", \"mutinynet\", \"signet\", \"regtest\"];\n/** Payments go through review and approval; only the SDK's own settlement (renewal, onboarding) reaches the provider directly. */\nclass PreviewProvider extends RestArkProvider {\n authorizedFinalize?:{txid:string;run:(checkpoints:string[])=>Promise<void>};\n captured?: { signedTx:string; checkpoints:string[] };\n take() { return this.captured; }\n override async submitTx(signedTx:string,checkpoints:string[]):Promise<never> { this.captured={signedTx,checkpoints};throw new PreparedOnly(); }\n override async finalizeTx(txid:string,checkpoints:string[]):Promise<void> {\n if(this.authorizedFinalize?.txid!==txid)throw new Error(\"Preview cannot finalize an unapproved payment\");\n await this.authorizedFinalize.run(checkpoints);\n }\n}\nexport class ArkadeAdapter implements PaymentAdapter<ArkPrepared> {\n readonly method = \"arkade\" as const;\n private pending:Promise<unknown>=Promise.resolve();\n private serial<T>(run:()=>Promise<T>):Promise<T>{const next=this.pending.then(run,run);this.pending=next.catch(()=>{});return next;}\n prepare(target:PaymentTarget,amount:number,feeCap:number){return this.serial(()=>this.prepareOperation(target,amount,feeCap));}\n execute(review:PaymentReview,prepared:ArkPrepared,persist?:()=>Promise<void>){return this.serial(()=>this.executeOperation(review,prepared,persist));}\n reconcile(review:PaymentReview,prepared:ArkPrepared,persist?:()=>Promise<void>){return this.serial(()=>this.reconcileOperation(review,prepared,persist));}\n private constructor(readonly config: ArkConfig, private identity: MnemonicIdentity, private wallet: Wallet, private preview: PreviewProvider, private provider: RestArkProvider, private indexer: RestIndexerProvider,private repository:NonNullable<StorageConfig[\"walletRepository\"]>) {}\n static async connect(config: ArkConfig, mnemonic: string, storage?: StorageConfig): Promise<ArkadeAdapter> {\n if (!ARK_NETWORKS.includes(config.network)) throw new Error(\"Unsupported Ark network\");\n validatePaymentTarget({method:\"arkade\",network:config.network,provider:config.provider,asset:\"BTC\",unit:\"sat\",address:\"configuration\",expiresAt:Date.now()+60000});\n validatePaymentTarget({method:\"arkade\",network:config.network,provider:config.explorer,asset:\"BTC\",unit:\"sat\",address:\"configuration\",expiresAt:Date.now()+60000});\n const provider=new RestArkProvider(config.provider);\n const info=await provider.getInfo();\n if(info.network!==config.network || info.signerPubkey!==config.serverKey) throw new Error(\"Ark provider network or signing key changed. Review the wallet configuration.\");\n const identity=MnemonicIdentity.fromMnemonic(mnemonic,{isMainnet:config.network===\"bitcoin\"});\n const preview=new PreviewProvider(config.provider);\n const indexer=new RestIndexerProvider(config.provider);\n const repositories={...storage,walletRepository:storage?.walletRepository??new IndexedDBWalletRepository(`ghostly-ark-${config.walletId}`),contractRepository:storage?.contractRepository??new IndexedDBContractRepository(`ghostly-ark-${config.walletId}`)};\n const wallet=await Wallet.create({identity,arkProvider:preview,indexerProvider:indexer,onchainProvider:new EsploraProvider(config.explorer),\n // Renews VTXOs before they expire and settles on-chain deposits into Ark, so a wallet left alone keeps its funds.\n // A local regtest server expires batches within minutes; there the test drives settlement itself.\n settlementConfig:config.network===\"regtest\"?false:{boardingUtxoSweep:true},walletMode:\"hd\",storage:repositories});\n return new ArkadeAdapter(config,identity,wallet,preview,provider,indexer,repositories.walletRepository);\n }\n async address() { return this.wallet.getAddress(); }\n requestAddress() { return this.serial(async()=>(await this.wallet.getNewAddresses({types:[\"default\"],forceNew:true}))[0].address); }\n async balance() { return (await this.wallet.getBalance()).available; }\n /**\n * Sats whose batch expired before they were renewed, in three kinds, as a person can act on them:\n * - `sweeping`: past their expiry, but the server has not swept their batch yet (with any swept ones: none can be\n * recovered meanwhile). The SDK (0.4.76) counts them as\n * recoverable already, but it settles an unswept coin without the forfeit the server still asks for (arkd wants\n * one until the sweep): the batch fails (\"missing forfeit transactions\") and the server bans the coin for a while.\n * The sweep waits for the chain's time to pass the expiry: an hour or more on a real network, the expiry's blocks\n * on regtest.\n * - `recoverable`: swept, and a recovery takes them now.\n * - `small`: swept, but together under the server's dust limit: a recovery makes one new coin and the server makes\n * none that small, so `recoverVtxos` answers \"No recoverable VTXOs found\". They wait for more coins to expire.\n */\n async expired() {\n const now={timestamp:new Date()};\n let swept=0,sweeping=0;\n for(const vtxo of await this.wallet.getVtxos({withRecoverable:true,withUnrolled:false})) {\n if(isRecoverable(vtxo))swept+=vtxo.value;\n else if(!vtxo.isSwept&&canRecoverOnchain(vtxo,now))sweeping+=vtxo.value;\n }\n // What a recovery would hand back, over the same coins it would take (only asked when none is still sweeping),\n // net of the server's fees: `recoverVtxos` refuses under the dust limit, where this still answers above 0.\n const takes=!sweeping&&swept>0&&(await (await this.wallet.getVtxoManager()).getRecoverableBalance()).recoverable>=this.wallet.dustAmount;\n // While any waits for the sweep, none can be recovered: all of them wait.\n return sweeping?{recoverable:0,sweeping:sweeping+swept,small:0}:{recoverable:takes?swept:0,sweeping:0,small:takes?0:swept};\n }\n /**\n * When the first coin still spendable expires (ms), or undefined with none: a coin left alone past it is swept by the\n * server and comes back only through a recovery. An expiry in blocks is read against the explorer's tip, at about ten\n * minutes a block.\n */\n async expiresAt():Promise<number|undefined> {\n let at:number|undefined, height:number|undefined;\n for(const vtxo of await this.wallet.getVtxos({withRecoverable:false,withUnrolled:false})) {\n if(vtxo.isSwept||vtxo.isSpent)continue;\n if(vtxo.expiresAt instanceof Date){const ms=vtxo.expiresAt.getTime();if(Number.isFinite(ms))at=Math.min(at??ms,ms);}\n else if(typeof vtxo.expiresAtHeight===\"number\")height=Math.min(height??vtxo.expiresAtHeight,vtxo.expiresAtHeight);\n }\n if(height!==undefined){\n const tip=(await new EsploraProvider(this.config.explorer).getChainTip()).height;\n const ms=Date.now()+Math.max(0,height-tip)*600_000;\n at=Math.min(at??ms,ms);\n }\n return at;\n }\n /**\n * Moves swept outputs back into the balance, through the server's next batch. Refused while any expired coin is\n * not swept yet (`recoverVtxos` would name it too, and that batch fails for all of them), and when what is swept\n * is too little for a batch.\n */\n recover() { return this.serial(async()=>{\n const {recoverable,sweeping,small}=await this.expired();\n if(sweeping)throw new Error(\"Expired coins can be recovered once the Ark server has swept their batch. Try again later.\");\n if(small)throw new Error(\"Too few expired sats to recover on their own. They are recovered with the next coins that expire.\");\n if(!recoverable)throw new Error(\"Nothing to recover\");\n return (await this.wallet.getVtxoManager()).recoverVtxos();\n }); }\n /** On-chain deposits land here; the SDK's settlement moves them into Ark once confirmed. */\n boardingAddress() { return this.wallet.getBoardingAddress(); }\n /** Sats sent on-chain to the boarding address that are not in Ark yet. */\n async incoming() { return (await this.wallet.getBalance()).boarding.total; }\n async dispose() { await this.wallet.dispose(); }\n private async prepareOperation(target:PaymentTarget,amount:number,feeCap:number):Promise<{fee:number;prepared:ArkPrepared}> {\n validatePaymentTarget(target); assertWholeSats(amount); this.checkTarget(target);\n const info=await this.provider.getInfo();\n if(info.network!==this.config.network || info.signerPubkey!==this.config.serverKey)throw new Error(\"Ark provider configuration changed\");\n if(await this.balance()<amount)throw new Error(\"Insufficient Ark balance\");\n this.preview.captured=undefined;\n try { await this.wallet.send({address:target.address,amount}); }\n catch(error) {\n if(!(error instanceof PreparedOnly)) {\n // Provider errors may embed signed payment material; expose only this bounded message.\n // eslint-disable-next-line preserve-caught-error\n throw new Error(\"Could not prepare this Ark payment. Check funds, address and provider.\");\n }\n }\n const capture=this.preview.take();\n if(!capture)throw new Error(\"The Ark adapter did not prepare a transaction\");\n const tx=Transaction.fromPSBT(unbase64(capture.signedTx));\n const destination=ArkAddress.decode(target.address);\n let received=0n,input=0n,output=0n;\n for(let i=0;i<tx.inputsLength;i++){const prev=tx.getInput(i).witnessUtxo;if(!prev)throw new Error(\"Missing Ark input amount\");input+=prev.amount;}\n for(let i=0;i<tx.outputsLength;i++){const out=tx.getOutput(i);output+=out.amount??0n;if(out.script && [hex(destination.pkScript),hex(destination.subdustPkScript)].includes(hex(out.script)))received+=out.amount??0n;}\n const fee=Number(input-output);\n if(received!==BigInt(amount) || !Number.isSafeInteger(fee) || fee<0 || fee>feeCap)throw new Error(\"Prepared amount or fee does not match your review\");\n return {fee,prepared:{...capture,txid:tx.id,address:target.address,amount,fee}};\n }\n private async executeOperation(review:PaymentReview,p:ArkPrepared,persist?:()=>Promise<void>) {\n this.checkPrepared(review,p);\n const info=await this.provider.getInfo();\n if(info.network!==this.config.network || info.signerPubkey!==this.config.serverKey)throw new Error(\"Ark provider configuration changed\");\n const tx=Transaction.fromPSBT(unbase64(p.signedTx));\n const response=await this.provider.submitTx(p.signedTx,p.checkpoints);\n assertSubmittedArkTxid(response,tx,\"Ghostly Ark submission\");\n if(!response.finalArkTx)throw new Error(\"Provider returned no signed transaction\");\n this.verifyServer(Transaction.fromPSBT(unbase64(response.finalArkTx)),tx);\n const matched=matchServerCheckpoints(response.signedCheckpointTxs,p.checkpoints.map(c=>Transaction.fromPSBT(unbase64(c))),\"Ghostly Ark checkpoints\");\n const contracts=await (await this.wallet.getContractManager()).getContracts();\n const final=await Promise.all(matched.map(async ({server,local})=>{\n assertAllowedSighashTypes(server);this.verifyServer(server,local);\n let signed=server;\n for(let index=0;index<local.inputsLength;index++){\n const script=local.getInput(index).witnessUtxo?.script;\n const contract=script && contracts.find(c=>c.script===hex(script));\n if(!contract || contract.type!==\"default\")throw new Error(\"Checkpoint is not owned by this wallet profile\");\n const descriptor=contract.metadata?.signingDescriptor;\n const signer=typeof descriptor===\"string\" ? await this.wallet.signerForDescriptor(descriptor) : contract.params.pubKey===hex(await this.identity.xOnlyPublicKey()) ? this.identity : undefined;\n if(!signer)throw new Error(\"Checkpoint signing descriptor missing\");\n signed=await signer.sign(signed,[index]);\n }\n return base64(signed.toPSBT());\n }));\n p.finalCheckpoints=final;\n await persist?.();\n await this.provider.finalizeTx(p.txid,final);\n return {txid:p.txid,settled:true};\n }\n private async reconcileOperation(review:PaymentReview,p:ArkPrepared,persist?:()=>Promise<void>) {\n this.checkPrepared(review,p);\n if(await this.verifyReceipt(p.txid,p.address,p.amount))return {txid:p.txid,settled:true};\n if(!p.finalCheckpoints){\n // An empty, lagging indexer result can clear the SDK hint. The durable\n // Ghostly journal is authoritative that this approved attempt is unresolved.\n const state=await this.repository.getWalletState();\n if(!state)throw new Error(\"Ark wallet state is unavailable\");\n await this.repository.saveWalletState({...state,settings:{...state.settings,hasPendingTx:true}});\n // Ask for pending transactions using SDK ownership proofs. Only the previously\n // approved transaction may finalize; submitTx remains permanently blocked here.\n this.preview.authorizedFinalize={txid:p.txid,run:async checkpoints=>{\n const matched=matchServerCheckpoints(checkpoints,p.checkpoints.map(c=>Transaction.fromPSBT(unbase64(c))),\"Ghostly pending recovery\");\n for(const {server,local} of matched){assertAllowedSighashTypes(server);this.verifyServer(server,local);}\n p.finalCheckpoints=checkpoints;await persist?.();\n await this.provider.finalizeTx(p.txid,checkpoints);\n }};\n try {await this.wallet.finalizePendingTxs();}finally{this.preview.authorizedFinalize=undefined;}\n }\n // Resume only the same finalization, never a new send/submit after ambiguity.\n if(p.finalCheckpoints) {\n try {await this.provider.finalizeTx(p.txid,p.finalCheckpoints);}\n catch { /* A finalized transaction can reject repeated finalization; verify its receipt. */ }\n }\n return {txid:p.txid,settled:await this.verifyReceipt(p.txid,p.address,p.amount)};\n }\n /**\n * A virtual output paying `address` at least `amount`, made after `since` and none of `claimed`: how a\n * request paid from another Ark wallet is seen, with no receipt from the payer.\n */\n async received(address:string,amount:number,since:number,claimed:ReadonlySet<string>):Promise<string|undefined> {\n const destination=ArkAddress.decode(address);\n const scripts=[hex(destination.pkScript),hex(destination.subdustPkScript)];\n const {vtxos}=await this.indexer.getVtxos({scripts});\n return vtxos.find(v=>/^[a-f0-9]{64}$/.test(v.txid) && v.value>=amount && scripts.includes(v.script) && !claimed.has(v.txid) && v.createdAt.getTime()>=since-60_000)?.txid;\n }\n async verifyReceipt(txid:string,address:string,amount:number):Promise<boolean> {\n if(!/^[a-f0-9]{64}$/.test(txid))return false;\n const destination=ArkAddress.decode(address);\n const result=await this.indexer.getVtxos({outpoints:[{txid,vout:0}]});\n return result.vtxos.some(v=>v.txid===txid && v.value===amount && [hex(destination.pkScript),hex(destination.subdustPkScript)].includes(v.script));\n }\n private checkTarget(t:PaymentTarget) {\n if(t.method!==this.method || t.provider!==this.config.provider || t.network!==this.config.network || t.asset!==\"BTC\" || t.unit!==\"sat\")throw new Error(\"Payment method, network or provider does not match this wallet\");\n const decoded=ArkAddress.decode(t.address);\n const pinned=this.config.serverKey.replace(/^(02|03)(?=[a-f0-9]{64}$)/,\"\");\n if(hex(decoded.serverPubKey)!==pinned || decoded.hrp!==(this.config.network===\"bitcoin\"?\"ark\":\"tark\"))throw new Error(\"Ark address belongs to another provider or network\");\n }\n private verifyServer(server:Transaction,local:Transaction) {\n const key=this.config.serverKey.replace(/^(02|03)(?=[a-f0-9]{64}$)/,\"\");\n for(let index=0;index<local.inputsLength;index++){\n const leaf=local.getInput(index).tapLeafScript?.[0]?.[1];\n if(!leaf)throw new Error(\"Missing reviewed spend leaf\");\n verifyTapscriptSignatures(server,index,[key],undefined,undefined,tapLeafHash(leaf.subarray(0,-1),leaf[leaf.length-1]));\n }\n }\n private checkPrepared(r:PaymentReview,p:ArkPrepared) {\n this.checkTarget(r);\n if(p.address!==r.address || p.amount!==r.amount || p.fee!==r.fee || p.fee>r.feeCap || Transaction.fromPSBT(unbase64(p.signedTx)).id!==p.txid)throw new Error(\"Prepared payment does not match the approved review\");\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"arkade-B-ALutmU.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/arkade.ts"],"sourcesContent":["import {\n Wallet, MnemonicIdentity, RestArkProvider, RestIndexerProvider, EsploraProvider,\n IndexedDBWalletRepository, IndexedDBContractRepository, ArkAddress, Transaction,\n assertSubmittedArkTxid, matchServerCheckpoints, assertAllowedSighashTypes, verifyTapscriptSignatures,\n isRecoverable, canRecoverOnchain, type StorageConfig,\n} from \"@arkade-os/sdk\";\nimport { tapLeafHash } from \"@scure/btc-signer/payment.js\";\nimport { assertWholeSats, validatePaymentTarget, type PaymentAdapter, type PaymentReview, type PaymentTarget } from \"@ghostly/core\";\n\nexport interface ArkConfig { network: \"bitcoin\" | \"regtest\" | \"signet\" | \"mutinynet\"; provider: string; explorer: string; serverKey: string; walletId: string }\nexport interface ArkPrepared { signedTx: string; checkpoints: string[]; txid: string; address: string; amount: number; fee: number; finalCheckpoints?: string[] }\nconst unbase64 = (s: string) => Uint8Array.from(atob(s),c=>c.charCodeAt(0));\nconst base64 = (bytes: Uint8Array) => btoa(Array.from(bytes,b=>String.fromCharCode(b)).join(\"\"));\nconst hex = (bytes: Uint8Array) => Array.from(bytes,b=>b.toString(16).padStart(2,\"0\")).join(\"\");\nclass PreparedOnly extends Error {}\nexport const ARK_NETWORKS: ArkConfig[\"network\"][] = [\"bitcoin\", \"mutinynet\", \"signet\", \"regtest\"];\n/** Payments go through review and approval; only the SDK's own settlement (renewal, onboarding) reaches the provider directly. */\nclass PreviewProvider extends RestArkProvider {\n authorizedFinalize?:{txid:string;run:(checkpoints:string[])=>Promise<void>};\n captured?: { signedTx:string; checkpoints:string[] };\n take() { return this.captured; }\n override async submitTx(signedTx:string,checkpoints:string[]):Promise<never> { this.captured={signedTx,checkpoints};throw new PreparedOnly(); }\n override async finalizeTx(txid:string,checkpoints:string[]):Promise<void> {\n if(this.authorizedFinalize?.txid!==txid)throw new Error(\"Preview cannot finalize an unapproved payment\");\n await this.authorizedFinalize.run(checkpoints);\n }\n}\nexport class ArkadeAdapter implements PaymentAdapter<ArkPrepared> {\n readonly method = \"arkade\" as const;\n private pending:Promise<unknown>=Promise.resolve();\n private serial<T>(run:()=>Promise<T>):Promise<T>{const next=this.pending.then(run,run);this.pending=next.catch(()=>{});return next;}\n prepare(target:PaymentTarget,amount:number,feeCap:number){return this.serial(()=>this.prepareOperation(target,amount,feeCap));}\n execute(review:PaymentReview,prepared:ArkPrepared,persist?:()=>Promise<void>){return this.serial(()=>this.executeOperation(review,prepared,persist));}\n reconcile(review:PaymentReview,prepared:ArkPrepared,persist?:()=>Promise<void>){return this.serial(()=>this.reconcileOperation(review,prepared,persist));}\n private constructor(readonly config: ArkConfig, private identity: MnemonicIdentity, private wallet: Wallet, private preview: PreviewProvider, private provider: RestArkProvider, private indexer: RestIndexerProvider,private repository:NonNullable<StorageConfig[\"walletRepository\"]>) {}\n static async connect(config: ArkConfig, mnemonic: string, storage?: StorageConfig): Promise<ArkadeAdapter> {\n if (!ARK_NETWORKS.includes(config.network)) throw new Error(\"Unsupported Ark network\");\n validatePaymentTarget({method:\"arkade\",network:config.network,provider:config.provider,asset:\"BTC\",unit:\"sat\",address:\"configuration\",expiresAt:Date.now()+60000});\n validatePaymentTarget({method:\"arkade\",network:config.network,provider:config.explorer,asset:\"BTC\",unit:\"sat\",address:\"configuration\",expiresAt:Date.now()+60000});\n const provider=new RestArkProvider(config.provider);\n const info=await provider.getInfo();\n if(info.network!==config.network || info.signerPubkey!==config.serverKey) throw new Error(\"Ark provider network or signing key changed. Review the wallet configuration.\");\n const identity=MnemonicIdentity.fromMnemonic(mnemonic,{isMainnet:config.network===\"bitcoin\"});\n const preview=new PreviewProvider(config.provider);\n const indexer=new RestIndexerProvider(config.provider);\n const repositories={...storage,walletRepository:storage?.walletRepository??new IndexedDBWalletRepository(`ghostly-ark-${config.walletId}`),contractRepository:storage?.contractRepository??new IndexedDBContractRepository(`ghostly-ark-${config.walletId}`)};\n const wallet=await Wallet.create({identity,arkProvider:preview,indexerProvider:indexer,onchainProvider:new EsploraProvider(config.explorer),\n // Renews VTXOs before they expire and settles on-chain deposits into Ark, so a wallet left alone keeps its funds.\n // A local regtest server expires batches within minutes; there the test drives settlement itself.\n settlementConfig:config.network===\"regtest\"?false:{boardingUtxoSweep:true},walletMode:\"hd\",storage:repositories});\n return new ArkadeAdapter(config,identity,wallet,preview,provider,indexer,repositories.walletRepository);\n }\n async address() { return this.wallet.getAddress(); }\n requestAddress() { return this.serial(async()=>(await this.wallet.getNewAddresses({types:[\"default\"],forceNew:true}))[0].address); }\n async balance() { return (await this.wallet.getBalance()).available; }\n /**\n * Sats whose batch expired before they were renewed, in three kinds, as a person can act on them:\n * - `sweeping`: past their expiry, but the server has not swept their batch yet (with any swept ones: none can be\n * recovered meanwhile). The SDK (0.4.76) counts them as\n * recoverable already, but it settles an unswept coin without the forfeit the server still asks for (arkd wants\n * one until the sweep): the batch fails (\"missing forfeit transactions\") and the server bans the coin for a while.\n * The sweep waits for the chain's time to pass the expiry: an hour or more on a real network, the expiry's blocks\n * on regtest.\n * - `recoverable`: swept, and a recovery takes them now.\n * - `small`: swept, but together under the server's dust limit: a recovery makes one new coin and the server makes\n * none that small, so `recoverVtxos` answers \"No recoverable VTXOs found\". They wait for more coins to expire.\n */\n async expired() {\n const now={timestamp:new Date()};\n let swept=0,sweeping=0;\n for(const vtxo of await this.wallet.getVtxos({withRecoverable:true,withUnrolled:false})) {\n if(isRecoverable(vtxo))swept+=vtxo.value;\n else if(!vtxo.isSwept&&canRecoverOnchain(vtxo,now))sweeping+=vtxo.value;\n }\n // What a recovery would hand back, over the same coins it would take (only asked when none is still sweeping),\n // net of the server's fees: `recoverVtxos` refuses under the dust limit, where this still answers above 0.\n const takes=!sweeping&&swept>0&&(await (await this.wallet.getVtxoManager()).getRecoverableBalance()).recoverable>=this.wallet.dustAmount;\n // While any waits for the sweep, none can be recovered: all of them wait.\n return sweeping?{recoverable:0,sweeping:sweeping+swept,small:0}:{recoverable:takes?swept:0,sweeping:0,small:takes?0:swept};\n }\n /**\n * When the first coin still spendable expires (ms), or undefined with none: a coin left alone past it is swept by the\n * server and comes back only through a recovery. An expiry in blocks is read against the explorer's tip, at about ten\n * minutes a block.\n */\n async expiresAt():Promise<number|undefined> {\n let at:number|undefined, height:number|undefined;\n for(const vtxo of await this.wallet.getVtxos({withRecoverable:false,withUnrolled:false})) {\n if(vtxo.isSwept||vtxo.isSpent)continue;\n if(vtxo.expiresAt instanceof Date){const ms=vtxo.expiresAt.getTime();if(Number.isFinite(ms))at=Math.min(at??ms,ms);}\n else if(typeof vtxo.expiresAtHeight===\"number\")height=Math.min(height??vtxo.expiresAtHeight,vtxo.expiresAtHeight);\n }\n if(height!==undefined){\n const tip=(await new EsploraProvider(this.config.explorer).getChainTip()).height;\n const ms=Date.now()+Math.max(0,height-tip)*600_000;\n at=Math.min(at??ms,ms);\n }\n return at;\n }\n /**\n * Moves swept outputs back into the balance, through the server's next batch. Refused while any expired coin is\n * not swept yet (`recoverVtxos` would name it too, and that batch fails for all of them), and when what is swept\n * is too little for a batch.\n */\n recover() { return this.serial(async()=>{\n const {recoverable,sweeping,small}=await this.expired();\n if(sweeping)throw new Error(\"Expired coins can be recovered once the Ark server has swept their batch. Try again later.\");\n if(small)throw new Error(\"Too few expired sats to recover on their own. They are recovered with the next coins that expire.\");\n if(!recoverable)throw new Error(\"Nothing to recover\");\n return (await this.wallet.getVtxoManager()).recoverVtxos();\n }); }\n /** On-chain deposits land here; the SDK's settlement moves them into Ark once confirmed. */\n boardingAddress() { return this.wallet.getBoardingAddress(); }\n /** Sats sent on-chain to the boarding address that are not in Ark yet. */\n async incoming() { return (await this.wallet.getBalance()).boarding.total; }\n async dispose() { await this.wallet.dispose(); }\n private async prepareOperation(target:PaymentTarget,amount:number,feeCap:number):Promise<{fee:number;prepared:ArkPrepared}> {\n validatePaymentTarget(target); assertWholeSats(amount); this.checkTarget(target);\n const info=await this.provider.getInfo();\n if(info.network!==this.config.network || info.signerPubkey!==this.config.serverKey)throw new Error(\"Ark provider configuration changed\");\n if(await this.balance()<amount)throw new Error(\"Insufficient Ark balance\");\n this.preview.captured=undefined;\n try { await this.wallet.send({address:target.address,amount}); }\n catch(error) {\n if(!(error instanceof PreparedOnly)) {\n // Provider errors may embed signed payment material; expose only this bounded message.\n // eslint-disable-next-line preserve-caught-error\n throw new Error(\"Could not prepare this Ark payment. Check funds, address and provider.\");\n }\n }\n const capture=this.preview.take();\n if(!capture)throw new Error(\"The Ark adapter did not prepare a transaction\");\n const tx=Transaction.fromPSBT(unbase64(capture.signedTx));\n const destination=ArkAddress.decode(target.address);\n let received=0n,input=0n,output=0n;\n for(let i=0;i<tx.inputsLength;i++){const prev=tx.getInput(i).witnessUtxo;if(!prev)throw new Error(\"Missing Ark input amount\");input+=prev.amount;}\n for(let i=0;i<tx.outputsLength;i++){const out=tx.getOutput(i);output+=out.amount??0n;if(out.script && [hex(destination.pkScript),hex(destination.subdustPkScript)].includes(hex(out.script)))received+=out.amount??0n;}\n const fee=Number(input-output);\n if(received!==BigInt(amount) || !Number.isSafeInteger(fee) || fee<0 || fee>feeCap)throw new Error(\"Prepared amount or fee does not match your review\");\n return {fee,prepared:{...capture,txid:tx.id,address:target.address,amount,fee}};\n }\n private async executeOperation(review:PaymentReview,p:ArkPrepared,persist?:()=>Promise<void>) {\n this.checkPrepared(review,p);\n const info=await this.provider.getInfo();\n if(info.network!==this.config.network || info.signerPubkey!==this.config.serverKey)throw new Error(\"Ark provider configuration changed\");\n const tx=Transaction.fromPSBT(unbase64(p.signedTx));\n const response=await this.provider.submitTx(p.signedTx,p.checkpoints);\n assertSubmittedArkTxid(response,tx,\"Ghostly Ark submission\");\n if(!response.finalArkTx)throw new Error(\"Provider returned no signed transaction\");\n this.verifyServer(Transaction.fromPSBT(unbase64(response.finalArkTx)),tx);\n const matched=matchServerCheckpoints(response.signedCheckpointTxs,p.checkpoints.map(c=>Transaction.fromPSBT(unbase64(c))),\"Ghostly Ark checkpoints\");\n const contracts=await (await this.wallet.getContractManager()).getContracts();\n const final=await Promise.all(matched.map(async ({server,local})=>{\n assertAllowedSighashTypes(server);this.verifyServer(server,local);\n let signed=server;\n for(let index=0;index<local.inputsLength;index++){\n const script=local.getInput(index).witnessUtxo?.script;\n const contract=script && contracts.find(c=>c.script===hex(script));\n if(!contract || contract.type!==\"default\")throw new Error(\"Checkpoint is not owned by this wallet profile\");\n const descriptor=contract.metadata?.signingDescriptor;\n const signer=typeof descriptor===\"string\" ? await this.wallet.signerForDescriptor(descriptor) : contract.params.pubKey===hex(await this.identity.xOnlyPublicKey()) ? this.identity : undefined;\n if(!signer)throw new Error(\"Checkpoint signing descriptor missing\");\n signed=await signer.sign(signed,[index]);\n }\n return base64(signed.toPSBT());\n }));\n p.finalCheckpoints=final;\n await persist?.();\n await this.provider.finalizeTx(p.txid,final);\n return {txid:p.txid,settled:true};\n }\n private async reconcileOperation(review:PaymentReview,p:ArkPrepared,persist?:()=>Promise<void>) {\n this.checkPrepared(review,p);\n if(await this.verifyReceipt(p.txid,p.address,p.amount))return {txid:p.txid,settled:true};\n if(!p.finalCheckpoints){\n // An empty, lagging indexer result can clear the SDK hint. The durable\n // Ghostly journal is authoritative that this approved attempt is unresolved.\n const state=await this.repository.getWalletState();\n if(!state)throw new Error(\"Ark wallet state is unavailable\");\n await this.repository.saveWalletState({...state,settings:{...state.settings,hasPendingTx:true}});\n // Ask for pending transactions using SDK ownership proofs. Only the previously\n // approved transaction may finalize; submitTx remains permanently blocked here.\n this.preview.authorizedFinalize={txid:p.txid,run:async checkpoints=>{\n const matched=matchServerCheckpoints(checkpoints,p.checkpoints.map(c=>Transaction.fromPSBT(unbase64(c))),\"Ghostly pending recovery\");\n for(const {server,local} of matched){assertAllowedSighashTypes(server);this.verifyServer(server,local);}\n p.finalCheckpoints=checkpoints;await persist?.();\n await this.provider.finalizeTx(p.txid,checkpoints);\n }};\n try {await this.wallet.finalizePendingTxs();}finally{this.preview.authorizedFinalize=undefined;}\n }\n // Resume only the same finalization, never a new send/submit after ambiguity.\n if(p.finalCheckpoints) {\n try {await this.provider.finalizeTx(p.txid,p.finalCheckpoints);}\n catch { /* A finalized transaction can reject repeated finalization; verify its receipt. */ }\n }\n return {txid:p.txid,settled:await this.verifyReceipt(p.txid,p.address,p.amount)};\n }\n /**\n * A virtual output paying `address` at least `amount`, made after `since` and none of `claimed`: how a\n * request paid from another Ark wallet is seen, with no receipt from the payer.\n */\n async received(address:string,amount:number,since:number,claimed:ReadonlySet<string>):Promise<string|undefined> {\n const destination=ArkAddress.decode(address);\n const scripts=[hex(destination.pkScript),hex(destination.subdustPkScript)];\n const {vtxos}=await this.indexer.getVtxos({scripts});\n return vtxos.find(v=>/^[a-f0-9]{64}$/.test(v.txid) && v.value>=amount && scripts.includes(v.script) && !claimed.has(v.txid) && v.createdAt.getTime()>=since-60_000)?.txid;\n }\n async verifyReceipt(txid:string,address:string,amount:number):Promise<boolean> {\n if(!/^[a-f0-9]{64}$/.test(txid))return false;\n const destination=ArkAddress.decode(address);\n const result=await this.indexer.getVtxos({outpoints:[{txid,vout:0}]});\n return result.vtxos.some(v=>v.txid===txid && v.value===amount && [hex(destination.pkScript),hex(destination.subdustPkScript)].includes(v.script));\n }\n private checkTarget(t:PaymentTarget) {\n if(t.method!==this.method || t.provider!==this.config.provider || t.network!==this.config.network || t.asset!==\"BTC\" || t.unit!==\"sat\")throw new Error(\"Payment method, network or provider does not match this wallet\");\n const decoded=ArkAddress.decode(t.address);\n const pinned=this.config.serverKey.replace(/^(02|03)(?=[a-f0-9]{64}$)/,\"\");\n if(hex(decoded.serverPubKey)!==pinned || decoded.hrp!==(this.config.network===\"bitcoin\"?\"ark\":\"tark\"))throw new Error(\"Ark address belongs to another provider or network\");\n }\n private verifyServer(server:Transaction,local:Transaction) {\n const key=this.config.serverKey.replace(/^(02|03)(?=[a-f0-9]{64}$)/,\"\");\n for(let index=0;index<local.inputsLength;index++){\n const leaf=local.getInput(index).tapLeafScript?.[0]?.[1];\n if(!leaf)throw new Error(\"Missing reviewed spend leaf\");\n verifyTapscriptSignatures(server,index,[key],undefined,undefined,tapLeafHash(leaf.subarray(0,-1),leaf[leaf.length-1]));\n }\n }\n private checkPrepared(r:PaymentReview,p:ArkPrepared) {\n this.checkTarget(r);\n if(p.address!==r.address || p.amount!==r.amount || p.fee!==r.fee || p.fee>r.feeCap || Transaction.fromPSBT(unbase64(p.signedTx)).id!==p.txid)throw new Error(\"Prepared payment does not match the approved review\");\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { Qc as fromBase64Url } from "./src-aKGjb_yz.js";
2
- import { n as hyperdhtRelayProblem } from "./server-D5FBZYW8.js";
1
+ import { ol as fromBase64Url } from "./src-_FzJkeAJ.js";
2
+ import { n as hyperdhtRelayProblem } from "./server-DHMXMwNn.js";
3
3
  import { ed25519 } from "@noble/curves/ed25519.js";
4
4
  //#region ../browser/src/platform/hyperdhtRelay.ts
5
5
  /**
@@ -339,4 +339,4 @@ async function createRelayedHyperEndpoint(seedB64, url, options = {}) {
339
339
  //#endregion
340
340
  export { createRelayedHyperEndpoint };
341
341
 
342
- //# sourceMappingURL=hyperdhtRelay-C0XRWySP.js.map
342
+ //# sourceMappingURL=hyperdhtRelay-hKC9DpIH.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hyperdhtRelay-C0XRWySP.js","names":[],"sources":["../../../browser/src/platform/hyperdhtRelay.ts"],"sourcesContent":["import { ed25519 } from \"@noble/curves/ed25519.js\";\nimport { fromBase64Url, type BoundChannel, type FrameChannel, type NativeEndpoint } from \"@ghostly/core\";\nimport { hyperdhtRelayProblem } from \"../shared/hyperdhtRelay\";\n\nexport { hyperdhtRelayProblem as relayUrlProblem };\n\n/**\n * HyperDHT (WISP 103) for a browser, through a dht-relay: a server that runs the UDP half of HyperDHT and\n * talks to the browser over one WebSocket. It runs non-custodial: the browser keeps its keys, runs the\n * Noise handshake and the encrypted stream itself, and the relay moves handshake messages and ciphertext.\n * The binding (the handshake hash and both keys) is computed here, at the endpoint, as the Desktop does.\n *\n * What the relay sees: this browser's address, the HyperDHT keys it listens on and dials, and when and how\n * much it sends. Not the frames, not the keys' secrets, not the handshake hash.\n *\n * The wire is the Desktop's (native/transports/hyperdht/endpoint.mjs): the same preface, u32 length-prefixed\n * UTF-8 frames of at most 60 KiB, so a browser and a Desktop talk HyperDHT to each other.\n */\n\nconst PREFACE = \"ghostly/paired-chat/1\";\nconst MAX_FRAME = 60 * 1024;\nconst SEND_BUDGET = 120 * 1024;\nconst HANDSHAKE_MS = 20_000;\nconst RELAY_OPEN_MS = 10_000;\nconst LISTEN_MS = 20_000;\nconst MAX_CHANNELS = 2;\n/**\n * How the relay client is always made: never custodial. dht-relay's default is custodial, which sends every\n * secret key to the relay so it can run the handshake and the stream itself. Nothing here takes this from\n * settings or options; test/hyperdhtRelay.test.ts fails if a secret key ever reaches the relay's wire.\n */\nconst NON_CUSTODIAL = Object.freeze({ custodial: false as const });\n\n/**\n * The descriptor a browser's HyperDHT endpoint gives out: its key, that it is reached only through a relay, and\n * which one (the connection panel names its host; a Desktop dialling it ignores both).\n */\nexport interface RelayedHyperDescriptor { publicKey: string; relayed: true; relay: string }\n\n/** The parts of dht-relay and its streams this adapter uses. */\ninterface SecretSocket {\n handshakeHash: Uint8Array | null;\n publicKey: Uint8Array;\n remotePublicKey: Uint8Array;\n opened?: Promise<boolean>;\n destroyed?: boolean;\n write(data: Uint8Array): boolean;\n destroy(error?: Error): void;\n on(event: string, listener: (...args: never[]) => void): unknown;\n once(event: string, listener: (...args: never[]) => void): unknown;\n off(event: string, listener: (...args: never[]) => void): unknown;\n}\ninterface RelayedServer {\n listen(keyPair: KeyPair): Promise<void>;\n close(): Promise<void>;\n}\ninterface RelayedNode {\n ready(): Promise<void>;\n connect(remotePublicKey: Uint8Array, options: { keyPair: KeyPair }): SecretSocket;\n createServer(options: object, onconnection: (socket: SecretSocket) => void): RelayedServer;\n destroy(): Promise<void>;\n}\ninterface KeyPair { publicKey: Uint8Array; secretKey: Uint8Array }\n\nconst hex = (bytes: Uint8Array) => Array.from(bytes, b => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst fromHex = (text: string) => Uint8Array.from(text.match(/../g)!, byte => parseInt(byte, 16));\n\n/** HyperDHT's key pair for a seed: libsodium's layout (the seed, then the public key). */\nexport function hyperKeyPair(seed: Uint8Array): KeyPair {\n if (seed.length !== 32) throw new Error(\"Invalid HyperDHT seed\");\n const publicKey = ed25519.getPublicKey(seed);\n const secretKey = new Uint8Array(64);\n secretKey.set(seed); secretKey.set(publicKey, 32);\n return { publicKey, secretKey };\n}\n\n/**\n * One WebSocket per relay for the whole app, shared by every chat's endpoint (each listens on its own\n * key). It closes when the last endpoint does. When the relay goes away, every endpoint on it says it is\n * unavailable, and the next one to start opens a new connection.\n */\ninterface RelayClient {\n node: RelayedNode;\n endpoints: Set<{ unavailable(): void }>;\n gone: boolean;\n /** Settles when the relay connection closes. */\n closed: Promise<void>;\n}\nconst clients = new Map<string, Promise<RelayClient>>();\n\nasync function relayClient(url: string, createSocket: (url: string) => WebSocket): Promise<RelayClient> {\n const existing = clients.get(url);\n if (existing) {\n const client = await existing.catch(() => null);\n if (client && !client.gone) return client;\n if (clients.get(url) === existing) clients.delete(url);\n }\n const opening = (async () => {\n const [{ default: RelayedDHT }, { default: WebSocketStream }] = await Promise.all([\n // @ts-expect-error dht-relay ships no types: RelayedNode and SecretSocket above describe what this uses.\n import(\"@hyperswarm/dht-relay\"),\n // @ts-expect-error The same package's WebSocket stream, untyped too.\n import(\"@hyperswarm/dht-relay/ws\"),\n ]);\n const socket = createSocket(url);\n socket.binaryType = \"arraybuffer\";\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => { socket.close(); reject(new Error(\"The HyperDHT relay did not answer\")); }, RELAY_OPEN_MS);\n socket.addEventListener(\"open\", () => { clearTimeout(timer); resolve(); }, { once: true });\n socket.addEventListener(\"error\", () => { clearTimeout(timer); reject(new Error(\"Could not reach the HyperDHT relay\")); }, { once: true });\n socket.addEventListener(\"close\", () => { clearTimeout(timer); reject(new Error(\"The HyperDHT relay closed the connection\")); }, { once: true });\n });\n // Non-custodial: the relay is told public keys only, never a secret key (dht-relay's default sends them).\n // The node's own key is never used to listen or dial: a random one says nothing about this app.\n const node = new RelayedDHT(new WebSocketStream(true, socket), { ...NON_CUSTODIAL, keyPair: hyperKeyPair(crypto.getRandomValues(new Uint8Array(32))) }) as RelayedNode;\n let closed!: () => void;\n const client: RelayClient = { node, endpoints: new Set(), gone: false, closed: new Promise(resolve => { closed = resolve; }) };\n socket.addEventListener(\"close\", () => {\n client.gone = true; closed();\n if (clients.get(url) === opening) clients.delete(url);\n for (const endpoint of [...client.endpoints]) endpoint.unavailable();\n });\n return client;\n })();\n clients.set(url, opening);\n opening.catch(() => { if (clients.get(url) === opening) clients.delete(url); });\n return opening;\n}\n\nfunction release(url: string, client: RelayClient, endpoint: { unavailable(): void }): void {\n client.endpoints.delete(endpoint);\n if (client.endpoints.size || client.gone) return;\n client.gone = true;\n void clients.get(url)?.then(current => { if (current === client) clients.delete(url); }).catch(() => {});\n void client.node.destroy().catch(() => {});\n}\n\n/** One authenticated stream as a frame channel, with a bounded queue before the session installs its reader. */\nclass RelayedChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: string[] = [];\n private closed = false;\n private pendingBytes = 0;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private readonly socket: SecretSocket) {\n socket.on(\"drain\", () => { this.pendingBytes = 0; });\n }\n get bufferedAmount(): number { return this.pendingBytes; }\n readonly sendBudget = SEND_BUDGET;\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n receive(text: string): boolean {\n if (this.closed) return true;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else return false;\n return true;\n }\n send(data: string | Uint8Array): void {\n if (typeof data !== \"string\" || this.closed || !data.length) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n const payload = new TextEncoder().encode(data);\n if (payload.length > MAX_FRAME || this.pendingBytes + payload.length > SEND_BUDGET) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n // The stream takes every write; past its high-water mark it says so until it drains.\n if (!writeFrame(this.socket, payload) || this.pendingBytes) this.pendingBytes += payload.length + 4;\n }\n drained(): Promise<void> {\n if (!this.pendingBytes) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const done = () => { this.socket.off(\"close\", gone); resolve(); };\n const gone = () => { this.socket.off(\"drain\", done); reject(new Error(\"Native channel closed\")); };\n this.socket.once(\"drain\", done); this.socket.once(\"close\", gone);\n });\n }\n closedByPeer(): void {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n this.onClose?.();\n }\n close(): void {\n if (this.closed) return;\n this.socket.destroy();\n this.closedByPeer();\n }\n}\n\nfunction writeFrame(socket: SecretSocket, payload: Uint8Array): boolean {\n const frame = new Uint8Array(4 + payload.length);\n new DataView(frame.buffer).setUint32(0, payload.length);\n frame.set(payload, 4);\n return socket.write(frame);\n}\n\n/**\n * Waits for the peer's preface on an authenticated stream (sending ours as soon as the handshake is\n * done), then hands the channel over with the binding the stream itself gives. A frame of the wrong size,\n * bytes that are not UTF-8 or another protocol close the stream.\n */\nfunction attach(socket: SecretSocket, channels: Set<RelayedChannel>): Promise<BoundChannel> {\n if (channels.size >= MAX_CHANNELS) { socket.destroy(); return Promise.reject(new Error(\"Native connection limit reached\")); }\n const channel = new RelayedChannel(socket);\n channels.add(channel);\n return new Promise<BoundChannel>((resolve, reject) => {\n let buffer = new Uint8Array(0);\n let prefaced = false;\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const timer = setTimeout(() => socket.destroy(new Error(\"HyperDHT handshake timed out\")), HANDSHAKE_MS);\n const fail = (error: Error) => { socket.destroy(error); };\n socket.on(\"error\", (error: Error) => reject(error));\n socket.on(\"close\", () => {\n clearTimeout(timer); channels.delete(channel);\n reject(new Error(\"Native channel closed\"));\n channel.closedByPeer();\n });\n const opened = () => {\n if (socket.handshakeHash?.length !== 64 || socket.publicKey?.length !== 32 || socket.remotePublicKey?.length !== 32) {\n fail(new Error(\"Noise session binding unavailable\")); return;\n }\n writeFrame(socket, new TextEncoder().encode(PREFACE));\n };\n if (socket.opened) void socket.opened.then(ok => { if (ok) opened(); else reject(new Error(\"Noise handshake failed\")); });\n else socket.once(\"open\", opened);\n socket.on(\"data\", (chunk: Uint8Array) => {\n const joined = new Uint8Array(buffer.length + chunk.length);\n joined.set(buffer); joined.set(chunk, buffer.length);\n buffer = joined;\n while (buffer.length >= 4) {\n const length = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(0);\n if (length === 0 || length > MAX_FRAME) { fail(new Error(\"Invalid frame length\")); return; }\n if (buffer.length < 4 + length) break;\n let text: string;\n try { text = decoder.decode(buffer.subarray(4, 4 + length)); }\n catch { fail(new Error(\"Invalid UTF-8 frame\")); return; }\n buffer = buffer.slice(4 + length);\n if (!prefaced) {\n if (text !== PREFACE) { fail(new Error(\"Unexpected application protocol\")); return; }\n prefaced = true; clearTimeout(timer);\n resolve({ channel, binding: { transport: \"hyperdht/1\", context: hex(socket.handshakeHash!),\n identities: [hex(socket.publicKey), hex(socket.remotePublicKey)].sort() as [string, string] } });\n } else if (!channel.receive(text)) { fail(new Error(\"Receive queue exceeded\")); return; }\n }\n });\n });\n}\n\nexport interface RelayedHyperOptions {\n /** How to open the WebSocket (tests pass Node's `ws`). Default: the browser's. */\n createSocket?: (url: string) => WebSocket;\n}\n\n/**\n * A chat's HyperDHT endpoint through the relay at `url`: it listens on the key of `seedB64` (the chat's\n * own transport seed, as on the Desktop) and dials a contact's descriptor. Fails, and so is not offered,\n * when the relay cannot be reached or does not let it listen.\n */\nexport async function createRelayedHyperEndpoint(seedB64: string, url: string, options: RelayedHyperOptions = {}): Promise<NativeEndpoint> {\n const problem = hyperdhtRelayProblem(url);\n if (problem) throw new Error(problem);\n const keyPair = hyperKeyPair(fromBase64Url(seedB64));\n const client = await relayClient(url, options.createSocket ?? (address => new WebSocket(address)));\n const channels = new Set<RelayedChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const waiting: BoundChannel[] = [];\n let endpoint: NativeEndpoint | null = null;\n const member = {\n unavailable() {\n if (stopped) return;\n stopped = true;\n for (const channel of channels) channel.close();\n endpoint?.onUnavailable?.();\n },\n };\n client.endpoints.add(member);\n const server = client.node.createServer({}, socket => {\n void attach(socket, channels).then(bound => {\n if (stopped) bound.channel.close();\n else if (handler) handler(bound);\n else if (waiting.length < MAX_CHANNELS) waiting.push(bound);\n else bound.channel.close();\n }).catch(() => {});\n });\n try {\n await Promise.race([\n server.listen(keyPair),\n client.closed.then(() => { throw new Error(\"The HyperDHT relay closed the connection\"); }),\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"The HyperDHT relay did not let this chat listen\")), LISTEN_MS)),\n ]);\n if (client.gone) throw new Error(\"The HyperDHT relay closed the connection\");\n } catch (error) {\n stopped = true;\n void server.close().catch(() => {});\n release(url, client, member);\n throw error;\n }\n const descriptor: RelayedHyperDescriptor = { publicKey: hex(keyPair.publicKey), relayed: true, relay: url };\n endpoint = {\n transport: \"hyperdht/1\", descriptor, onDescriptor: null, onUnavailable: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of waiting.splice(0)) value(bound); },\n async connect(address) {\n const publicKey = (address as { publicKey?: unknown } | null)?.publicKey;\n if (stopped || typeof publicKey !== \"string\" || !/^[a-f0-9]{64}$/.test(publicKey)) throw new Error(\"Invalid HyperDHT endpoint\");\n return attach(client.node.connect(fromHex(publicKey), { keyPair }), channels);\n },\n async close() {\n stopped = true;\n for (const channel of channels) channel.close();\n waiting.length = 0;\n await server.close().catch(() => {});\n release(url, client, member);\n },\n };\n return endpoint;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hyperdhtRelay-hKC9DpIH.js","names":[],"sources":["../../../browser/src/platform/hyperdhtRelay.ts"],"sourcesContent":["import { ed25519 } from \"@noble/curves/ed25519.js\";\nimport { fromBase64Url, type BoundChannel, type FrameChannel, type NativeEndpoint } from \"@ghostly/core\";\nimport { hyperdhtRelayProblem } from \"../shared/hyperdhtRelay\";\n\nexport { hyperdhtRelayProblem as relayUrlProblem };\n\n/**\n * HyperDHT (WISP 103) for a browser, through a dht-relay: a server that runs the UDP half of HyperDHT and\n * talks to the browser over one WebSocket. It runs non-custodial: the browser keeps its keys, runs the\n * Noise handshake and the encrypted stream itself, and the relay moves handshake messages and ciphertext.\n * The binding (the handshake hash and both keys) is computed here, at the endpoint, as the Desktop does.\n *\n * What the relay sees: this browser's address, the HyperDHT keys it listens on and dials, and when and how\n * much it sends. Not the frames, not the keys' secrets, not the handshake hash.\n *\n * The wire is the Desktop's (native/transports/hyperdht/endpoint.mjs): the same preface, u32 length-prefixed\n * UTF-8 frames of at most 60 KiB, so a browser and a Desktop talk HyperDHT to each other.\n */\n\nconst PREFACE = \"ghostly/paired-chat/1\";\nconst MAX_FRAME = 60 * 1024;\nconst SEND_BUDGET = 120 * 1024;\nconst HANDSHAKE_MS = 20_000;\nconst RELAY_OPEN_MS = 10_000;\nconst LISTEN_MS = 20_000;\nconst MAX_CHANNELS = 2;\n/**\n * How the relay client is always made: never custodial. dht-relay's default is custodial, which sends every\n * secret key to the relay so it can run the handshake and the stream itself. Nothing here takes this from\n * settings or options; test/hyperdhtRelay.test.ts fails if a secret key ever reaches the relay's wire.\n */\nconst NON_CUSTODIAL = Object.freeze({ custodial: false as const });\n\n/**\n * The descriptor a browser's HyperDHT endpoint gives out: its key, that it is reached only through a relay, and\n * which one (the connection panel names its host; a Desktop dialling it ignores both).\n */\nexport interface RelayedHyperDescriptor { publicKey: string; relayed: true; relay: string }\n\n/** The parts of dht-relay and its streams this adapter uses. */\ninterface SecretSocket {\n handshakeHash: Uint8Array | null;\n publicKey: Uint8Array;\n remotePublicKey: Uint8Array;\n opened?: Promise<boolean>;\n destroyed?: boolean;\n write(data: Uint8Array): boolean;\n destroy(error?: Error): void;\n on(event: string, listener: (...args: never[]) => void): unknown;\n once(event: string, listener: (...args: never[]) => void): unknown;\n off(event: string, listener: (...args: never[]) => void): unknown;\n}\ninterface RelayedServer {\n listen(keyPair: KeyPair): Promise<void>;\n close(): Promise<void>;\n}\ninterface RelayedNode {\n ready(): Promise<void>;\n connect(remotePublicKey: Uint8Array, options: { keyPair: KeyPair }): SecretSocket;\n createServer(options: object, onconnection: (socket: SecretSocket) => void): RelayedServer;\n destroy(): Promise<void>;\n}\ninterface KeyPair { publicKey: Uint8Array; secretKey: Uint8Array }\n\nconst hex = (bytes: Uint8Array) => Array.from(bytes, b => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst fromHex = (text: string) => Uint8Array.from(text.match(/../g)!, byte => parseInt(byte, 16));\n\n/** HyperDHT's key pair for a seed: libsodium's layout (the seed, then the public key). */\nexport function hyperKeyPair(seed: Uint8Array): KeyPair {\n if (seed.length !== 32) throw new Error(\"Invalid HyperDHT seed\");\n const publicKey = ed25519.getPublicKey(seed);\n const secretKey = new Uint8Array(64);\n secretKey.set(seed); secretKey.set(publicKey, 32);\n return { publicKey, secretKey };\n}\n\n/**\n * One WebSocket per relay for the whole app, shared by every chat's endpoint (each listens on its own\n * key). It closes when the last endpoint does. When the relay goes away, every endpoint on it says it is\n * unavailable, and the next one to start opens a new connection.\n */\ninterface RelayClient {\n node: RelayedNode;\n endpoints: Set<{ unavailable(): void }>;\n gone: boolean;\n /** Settles when the relay connection closes. */\n closed: Promise<void>;\n}\nconst clients = new Map<string, Promise<RelayClient>>();\n\nasync function relayClient(url: string, createSocket: (url: string) => WebSocket): Promise<RelayClient> {\n const existing = clients.get(url);\n if (existing) {\n const client = await existing.catch(() => null);\n if (client && !client.gone) return client;\n if (clients.get(url) === existing) clients.delete(url);\n }\n const opening = (async () => {\n const [{ default: RelayedDHT }, { default: WebSocketStream }] = await Promise.all([\n // @ts-expect-error dht-relay ships no types: RelayedNode and SecretSocket above describe what this uses.\n import(\"@hyperswarm/dht-relay\"),\n // @ts-expect-error The same package's WebSocket stream, untyped too.\n import(\"@hyperswarm/dht-relay/ws\"),\n ]);\n const socket = createSocket(url);\n socket.binaryType = \"arraybuffer\";\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => { socket.close(); reject(new Error(\"The HyperDHT relay did not answer\")); }, RELAY_OPEN_MS);\n socket.addEventListener(\"open\", () => { clearTimeout(timer); resolve(); }, { once: true });\n socket.addEventListener(\"error\", () => { clearTimeout(timer); reject(new Error(\"Could not reach the HyperDHT relay\")); }, { once: true });\n socket.addEventListener(\"close\", () => { clearTimeout(timer); reject(new Error(\"The HyperDHT relay closed the connection\")); }, { once: true });\n });\n // Non-custodial: the relay is told public keys only, never a secret key (dht-relay's default sends them).\n // The node's own key is never used to listen or dial: a random one says nothing about this app.\n const node = new RelayedDHT(new WebSocketStream(true, socket), { ...NON_CUSTODIAL, keyPair: hyperKeyPair(crypto.getRandomValues(new Uint8Array(32))) }) as RelayedNode;\n let closed!: () => void;\n const client: RelayClient = { node, endpoints: new Set(), gone: false, closed: new Promise(resolve => { closed = resolve; }) };\n socket.addEventListener(\"close\", () => {\n client.gone = true; closed();\n if (clients.get(url) === opening) clients.delete(url);\n for (const endpoint of [...client.endpoints]) endpoint.unavailable();\n });\n return client;\n })();\n clients.set(url, opening);\n opening.catch(() => { if (clients.get(url) === opening) clients.delete(url); });\n return opening;\n}\n\nfunction release(url: string, client: RelayClient, endpoint: { unavailable(): void }): void {\n client.endpoints.delete(endpoint);\n if (client.endpoints.size || client.gone) return;\n client.gone = true;\n void clients.get(url)?.then(current => { if (current === client) clients.delete(url); }).catch(() => {});\n void client.node.destroy().catch(() => {});\n}\n\n/** One authenticated stream as a frame channel, with a bounded queue before the session installs its reader. */\nclass RelayedChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: string[] = [];\n private closed = false;\n private pendingBytes = 0;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private readonly socket: SecretSocket) {\n socket.on(\"drain\", () => { this.pendingBytes = 0; });\n }\n get bufferedAmount(): number { return this.pendingBytes; }\n readonly sendBudget = SEND_BUDGET;\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n receive(text: string): boolean {\n if (this.closed) return true;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else return false;\n return true;\n }\n send(data: string | Uint8Array): void {\n if (typeof data !== \"string\" || this.closed || !data.length) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n const payload = new TextEncoder().encode(data);\n if (payload.length > MAX_FRAME || this.pendingBytes + payload.length > SEND_BUDGET) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n // The stream takes every write; past its high-water mark it says so until it drains.\n if (!writeFrame(this.socket, payload) || this.pendingBytes) this.pendingBytes += payload.length + 4;\n }\n drained(): Promise<void> {\n if (!this.pendingBytes) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const done = () => { this.socket.off(\"close\", gone); resolve(); };\n const gone = () => { this.socket.off(\"drain\", done); reject(new Error(\"Native channel closed\")); };\n this.socket.once(\"drain\", done); this.socket.once(\"close\", gone);\n });\n }\n closedByPeer(): void {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n this.onClose?.();\n }\n close(): void {\n if (this.closed) return;\n this.socket.destroy();\n this.closedByPeer();\n }\n}\n\nfunction writeFrame(socket: SecretSocket, payload: Uint8Array): boolean {\n const frame = new Uint8Array(4 + payload.length);\n new DataView(frame.buffer).setUint32(0, payload.length);\n frame.set(payload, 4);\n return socket.write(frame);\n}\n\n/**\n * Waits for the peer's preface on an authenticated stream (sending ours as soon as the handshake is\n * done), then hands the channel over with the binding the stream itself gives. A frame of the wrong size,\n * bytes that are not UTF-8 or another protocol close the stream.\n */\nfunction attach(socket: SecretSocket, channels: Set<RelayedChannel>): Promise<BoundChannel> {\n if (channels.size >= MAX_CHANNELS) { socket.destroy(); return Promise.reject(new Error(\"Native connection limit reached\")); }\n const channel = new RelayedChannel(socket);\n channels.add(channel);\n return new Promise<BoundChannel>((resolve, reject) => {\n let buffer = new Uint8Array(0);\n let prefaced = false;\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const timer = setTimeout(() => socket.destroy(new Error(\"HyperDHT handshake timed out\")), HANDSHAKE_MS);\n const fail = (error: Error) => { socket.destroy(error); };\n socket.on(\"error\", (error: Error) => reject(error));\n socket.on(\"close\", () => {\n clearTimeout(timer); channels.delete(channel);\n reject(new Error(\"Native channel closed\"));\n channel.closedByPeer();\n });\n const opened = () => {\n if (socket.handshakeHash?.length !== 64 || socket.publicKey?.length !== 32 || socket.remotePublicKey?.length !== 32) {\n fail(new Error(\"Noise session binding unavailable\")); return;\n }\n writeFrame(socket, new TextEncoder().encode(PREFACE));\n };\n if (socket.opened) void socket.opened.then(ok => { if (ok) opened(); else reject(new Error(\"Noise handshake failed\")); });\n else socket.once(\"open\", opened);\n socket.on(\"data\", (chunk: Uint8Array) => {\n const joined = new Uint8Array(buffer.length + chunk.length);\n joined.set(buffer); joined.set(chunk, buffer.length);\n buffer = joined;\n while (buffer.length >= 4) {\n const length = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(0);\n if (length === 0 || length > MAX_FRAME) { fail(new Error(\"Invalid frame length\")); return; }\n if (buffer.length < 4 + length) break;\n let text: string;\n try { text = decoder.decode(buffer.subarray(4, 4 + length)); }\n catch { fail(new Error(\"Invalid UTF-8 frame\")); return; }\n buffer = buffer.slice(4 + length);\n if (!prefaced) {\n if (text !== PREFACE) { fail(new Error(\"Unexpected application protocol\")); return; }\n prefaced = true; clearTimeout(timer);\n resolve({ channel, binding: { transport: \"hyperdht/1\", context: hex(socket.handshakeHash!),\n identities: [hex(socket.publicKey), hex(socket.remotePublicKey)].sort() as [string, string] } });\n } else if (!channel.receive(text)) { fail(new Error(\"Receive queue exceeded\")); return; }\n }\n });\n });\n}\n\nexport interface RelayedHyperOptions {\n /** How to open the WebSocket (tests pass Node's `ws`). Default: the browser's. */\n createSocket?: (url: string) => WebSocket;\n}\n\n/**\n * A chat's HyperDHT endpoint through the relay at `url`: it listens on the key of `seedB64` (the chat's\n * own transport seed, as on the Desktop) and dials a contact's descriptor. Fails, and so is not offered,\n * when the relay cannot be reached or does not let it listen.\n */\nexport async function createRelayedHyperEndpoint(seedB64: string, url: string, options: RelayedHyperOptions = {}): Promise<NativeEndpoint> {\n const problem = hyperdhtRelayProblem(url);\n if (problem) throw new Error(problem);\n const keyPair = hyperKeyPair(fromBase64Url(seedB64));\n const client = await relayClient(url, options.createSocket ?? (address => new WebSocket(address)));\n const channels = new Set<RelayedChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const waiting: BoundChannel[] = [];\n let endpoint: NativeEndpoint | null = null;\n const member = {\n unavailable() {\n if (stopped) return;\n stopped = true;\n for (const channel of channels) channel.close();\n endpoint?.onUnavailable?.();\n },\n };\n client.endpoints.add(member);\n const server = client.node.createServer({}, socket => {\n void attach(socket, channels).then(bound => {\n if (stopped) bound.channel.close();\n else if (handler) handler(bound);\n else if (waiting.length < MAX_CHANNELS) waiting.push(bound);\n else bound.channel.close();\n }).catch(() => {});\n });\n try {\n await Promise.race([\n server.listen(keyPair),\n client.closed.then(() => { throw new Error(\"The HyperDHT relay closed the connection\"); }),\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"The HyperDHT relay did not let this chat listen\")), LISTEN_MS)),\n ]);\n if (client.gone) throw new Error(\"The HyperDHT relay closed the connection\");\n } catch (error) {\n stopped = true;\n void server.close().catch(() => {});\n release(url, client, member);\n throw error;\n }\n const descriptor: RelayedHyperDescriptor = { publicKey: hex(keyPair.publicKey), relayed: true, relay: url };\n endpoint = {\n transport: \"hyperdht/1\", descriptor, onDescriptor: null, onUnavailable: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of waiting.splice(0)) value(bound); },\n async connect(address) {\n const publicKey = (address as { publicKey?: unknown } | null)?.publicKey;\n if (stopped || typeof publicKey !== \"string\" || !/^[a-f0-9]{64}$/.test(publicKey)) throw new Error(\"Invalid HyperDHT endpoint\");\n return attach(client.node.connect(fromHex(publicKey), { keyPair }), channels);\n },\n async close() {\n stopped = true;\n for (const channel of channels) channel.close();\n waiting.length = 0;\n await server.close().catch(() => {});\n release(url, client, member);\n },\n };\n return endpoint;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Ac as RelayTransport, Ic as isDiscoveryBudgetError, Uc as parseRelayPayload, Vc as newerPacket, qc as publicKeyFromZ32 } from "./src-aKGjb_yz.js";
1
+ import { Rc as RelayTransport, Wc as isDiscoveryBudgetError, Yc as newerPacket, Zc as parseRelayPayload, tl as publicKeyFromZ32 } from "./src-_FzJkeAJ.js";
2
2
  import { ed25519 } from "@noble/curves/ed25519.js";
3
3
  import { createHash } from "node:crypto";
4
4
  import DHT from "bittorrent-dht";
@@ -187,4 +187,4 @@ var RelaysAndDht = class {
187
187
  //#endregion
188
188
  export { Mainline, RelaysAndDht };
189
189
 
190
- //# sourceMappingURL=mainline-DVYdrus6.js.map
190
+ //# sourceMappingURL=mainline-DriRu_hb.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mainline-DVYdrus6.js","names":[],"sources":["../../src/runtime/mainline.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport DHT from \"bittorrent-dht\";\nimport { ed25519 } from \"@noble/curves/ed25519.js\";\nimport {\n RelayTransport, isDiscoveryBudgetError, newerPacket, parseRelayPayload, publicKeyFromZ32,\n type DiscoveryStatus, type GhostRecord, type Identity, type PkarrRequestOptions, type PkarrTransport, type RelayTransportOptions, type SignedPacket,\n} from \"@ghostly/core\";\n\n/**\n * Pkarr over the Mainline DHT (BEP44), from Node, as the Desktop reaches it from Rust: the relays are HTTP bridges to\n * this same DHT, so a headless Ghostly that speaks it directly keeps finding its contacts while the relays fail (they\n * answered HTTP 500 for more than ten minutes on 2026-09-27, and a group's edges stayed down all that while).\n *\n * A Pkarr packet is a BEP44 mutable item: the key is the Ed25519 public key, the value the DNS packet, the sequence\n * number its timestamp in microseconds, and the signature the one the relay payload already carries (over\n * `3:seqi<seq>e1:v<len>:<v>`). So one signed payload goes to the relays and to the DHT unchanged.\n */\n\nexport interface MainlineOptions {\n /** `host:port` nodes to start from; the public routers when absent; none with `false` (a testnet's first node). */\n bootstrap?: string[] | false;\n /** Address to bind (a loopback testnet binds 127.0.0.1). */\n host?: string;\n /** How long a lookup may take before it is given up, in ms. */\n timeoutMs?: number;\n}\n\nconst SIGNATURE = 64, TIMESTAMP = 8;\nconst verify = (sig: Uint8Array, message: Uint8Array, key: Uint8Array) => { try { return ed25519.verify(sig, message, key); } catch { return false; } };\n\n/** A DHT node of this process: puts and gets Pkarr packets. */\nexport class Mainline {\n private readonly dht: DHT;\n /** Its UDP socket is bound. */\n readonly listening: Promise<void>;\n private readonly timeoutMs: number;\n\n constructor(options: MainlineOptions = {}) {\n this.timeoutMs = options.timeoutMs ?? 20_000;\n const bootstrap = options.bootstrap === false ? false : options.bootstrap?.length ? options.bootstrap : undefined;\n this.dht = new DHT({ verify, ...(bootstrap !== undefined ? { bootstrap } : {}), ...(options.host ? { host: options.host } : {}) });\n this.listening = new Promise((resolve) => this.dht.listen(0, options.host, () => resolve()));\n // A node of the table that errors must not end the process.\n this.dht.on(\"error\", () => {});\n }\n\n /** Adds a node (a testnet's peers). */\n addNode(host: string, port: number): void { this.dht.addNode({ host, port }); }\n address(): { port: number } { return this.dht.address() as { port: number }; }\n\n /** Puts a relay payload (`<signature><timestamp><DNS packet>`) as the mutable item of `pubKeyZ32`; resolves with the nodes that took it. */\n put(pubKeyZ32: string, payload: Uint8Array): Promise<number> {\n const k = Buffer.from(publicKeyFromZ32(pubKeyZ32));\n const sig = Buffer.from(payload.subarray(0, SIGNATURE));\n const seq = Number(new DataView(payload.buffer, payload.byteOffset + SIGNATURE, TIMESTAMP).getBigUint64(0));\n const v = Buffer.from(payload.subarray(SIGNATURE + TIMESTAMP));\n return this.bounded(new Promise((resolve, reject) => {\n this.dht.put({ k, v, seq, sig }, (error, _hash, nodes) => {\n if (error) reject(error); else if (!nodes) reject(new Error(\"No DHT node took the packet\")); else resolve(nodes);\n });\n }));\n }\n\n /** The newest packet of `pubKeyZ32` the DHT holds, checked; null when nobody has one. */\n get(pubKeyZ32: string): Promise<SignedPacket | null> {\n const k = Buffer.from(publicKeyFromZ32(pubKeyZ32));\n // A mutable item without salt is found under the SHA-1 of its public key (BEP44).\n const target = createHash(\"sha1\").update(k).digest();\n return this.bounded(new Promise((resolve, reject) => {\n this.dht.get(target, { cache: false }, (error, found) => {\n if (error) return reject(error);\n if (!found?.v || !found.sig) return resolve(null);\n const timestamp = new Uint8Array(TIMESTAMP);\n new DataView(timestamp.buffer).setBigUint64(0, BigInt(found.seq));\n const payload = new Uint8Array(SIGNATURE + TIMESTAMP + found.v.length);\n payload.set(found.sig, 0); payload.set(timestamp, SIGNATURE); payload.set(found.v, SIGNATURE + TIMESTAMP);\n try { resolve(parseRelayPayload(pubKeyZ32, payload)); } catch { resolve(null); }\n });\n }));\n }\n\n private bounded<T>(work: Promise<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(\"DHT lookup timed out\")), this.timeoutMs);\n work.then((value) => { clearTimeout(timer); resolve(value); }, (error) => { clearTimeout(timer); reject(error); });\n });\n }\n\n destroy(): Promise<void> { return new Promise((resolve) => this.dht.destroy(() => resolve())); }\n}\n\n/**\n * The relays first, as the web app reaches Pkarr (its contacts read only the relays), and the DHT beside them: every\n * packet also goes to the DHT, and a read goes there when every relay is failing or the read failed for anything but\n * this app's own request budget (that is a wait, not an outage).\n *\n * `pinned`: the relays given are the only ones (`GHOSTLY_PKARR_RELAYS`, a private network): the profile's `relays`\n * setting does not replace them. With no DHT (`dht` null), the relays alone carry every packet.\n */\nexport class RelaysAndDht implements PkarrTransport {\n readonly relays: RelayTransport;\n private lastVia: \"relay\" | \"dht\" = \"relay\";\n\n constructor(private readonly dht: Mainline | null, relays: RelayTransportOptions = {}, private readonly pinned = false) {\n this.relays = new RelayTransport(relays);\n }\n\n async publish(identity: Identity, records: GhostRecord[], options: PkarrRequestOptions = {}): Promise<void> {\n // BEP44 sequence numbers must strictly increase, on the relays and on the DHT alike: one payload for both, dated\n // past every packet put or read under the key (`RelayTransport.publishDated`), whichever clock dated those.\n await this.relays.publishDated(identity, records, (key, payload, _timestamp, conflict) => this.putBoth(key, payload, options, conflict), options);\n }\n\n publishPayload(pubKeyZ32: string, payload: Uint8Array, options: PkarrRequestOptions = {}): Promise<void> {\n return this.putBoth(pubKeyZ32, payload, options);\n }\n\n private async putBoth(pubKeyZ32: string, payload: Uint8Array, options: PkarrRequestOptions, conflict?: (relay: string) => void): Promise<void> {\n if (!this.dht) return this.relays.publishPayload(pubKeyZ32, payload, options, conflict);\n const onDht = this.dht.put(pubKeyZ32, payload);\n onDht.catch(() => {});\n try {\n await this.relays.publishPayload(pubKeyZ32, payload, options, conflict);\n } catch (error) {\n // Held back by this app's budget: the relays take it when it frees (browser contacts read only them).\n if (isDiscoveryBudgetError(error)) throw error;\n // The relays failed: out on the DHT is out (a Pkarr relay reads the DHT for a key it has not got).\n await onDht.catch(() => { throw error; });\n }\n }\n\n async resolve(pubKeyZ32: string, options: PkarrRequestOptions = {}): Promise<SignedPacket | null> {\n if (!this.dht) return this.relays.resolve(pubKeyZ32, options);\n let fromRelays: SignedPacket | null = null, failed: unknown = null;\n // Every relay left alone for failing: the relays answer from what they last saw, if anything; the DHT is asked.\n const down = () => this.relays.discovery().relays.every((relay) => relay.state === \"failing\");\n if (!down()) {\n try { fromRelays = await this.relays.resolve(pubKeyZ32, options); this.lastVia = \"relay\"; } catch (error) {\n if (isDiscoveryBudgetError(error)) throw error;\n failed = error;\n }\n if (!failed && !down()) return fromRelays;\n }\n try {\n const fromDht = await this.dht.get(pubKeyZ32);\n this.lastVia = \"dht\";\n if (fromDht) this.relays.sawTimestamp(pubKeyZ32, fromDht.timestampMicros);\n return newerPacket(fromRelays, fromDht);\n } catch (error) {\n if (fromRelays) return fromRelays;\n throw failed ?? error;\n }\n }\n\n describe(): { protocol: string; relays: string[] } {\n return { protocol: this.dht ? \"Pkarr relays (HTTP), and the Mainline DHT (BEP44) directly\" : \"Pkarr relays (HTTP)\", relays: this.relays.describe().relays };\n }\n discovery(): DiscoveryStatus {\n const status = this.relays.discovery();\n return this.lastVia === \"dht\" ? { ...status, path: { via: \"dht\" } } : status;\n }\n subscribe(listener: Parameters<RelayTransport[\"subscribe\"]>[0]): () => void { return this.relays.subscribe(listener); }\n /** A relay's answer only: a packet the DHT hands back when the relays are down may be older than one already read. */\n readAnsweredAt(pubKeyZ32: string): number | undefined { return this.relays.readAnsweredAt(pubKeyZ32); }\n onServerTime(listener: Parameters<RelayTransport[\"onServerTime\"]>[0]): () => void { return this.relays.onServerTime(listener); }\n configure({ relays }: { relays: string[]; readRelays: boolean }): void { if (!this.pinned) this.relays.setRelays(relays); }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mainline-DriRu_hb.js","names":[],"sources":["../../src/runtime/mainline.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport DHT from \"bittorrent-dht\";\nimport { ed25519 } from \"@noble/curves/ed25519.js\";\nimport {\n RelayTransport, isDiscoveryBudgetError, newerPacket, parseRelayPayload, publicKeyFromZ32,\n type DiscoveryStatus, type GhostRecord, type Identity, type PkarrRequestOptions, type PkarrTransport, type RelayTransportOptions, type SignedPacket,\n} from \"@ghostly/core\";\n\n/**\n * Pkarr over the Mainline DHT (BEP44), from Node, as the Desktop reaches it from Rust: the relays are HTTP bridges to\n * this same DHT, so a headless Ghostly that speaks it directly keeps finding its contacts while the relays fail (they\n * answered HTTP 500 for more than ten minutes on 2026-09-27, and a group's edges stayed down all that while).\n *\n * A Pkarr packet is a BEP44 mutable item: the key is the Ed25519 public key, the value the DNS packet, the sequence\n * number its timestamp in microseconds, and the signature the one the relay payload already carries (over\n * `3:seqi<seq>e1:v<len>:<v>`). So one signed payload goes to the relays and to the DHT unchanged.\n */\n\nexport interface MainlineOptions {\n /** `host:port` nodes to start from; the public routers when absent; none with `false` (a testnet's first node). */\n bootstrap?: string[] | false;\n /** Address to bind (a loopback testnet binds 127.0.0.1). */\n host?: string;\n /** How long a lookup may take before it is given up, in ms. */\n timeoutMs?: number;\n}\n\nconst SIGNATURE = 64, TIMESTAMP = 8;\nconst verify = (sig: Uint8Array, message: Uint8Array, key: Uint8Array) => { try { return ed25519.verify(sig, message, key); } catch { return false; } };\n\n/** A DHT node of this process: puts and gets Pkarr packets. */\nexport class Mainline {\n private readonly dht: DHT;\n /** Its UDP socket is bound. */\n readonly listening: Promise<void>;\n private readonly timeoutMs: number;\n\n constructor(options: MainlineOptions = {}) {\n this.timeoutMs = options.timeoutMs ?? 20_000;\n const bootstrap = options.bootstrap === false ? false : options.bootstrap?.length ? options.bootstrap : undefined;\n this.dht = new DHT({ verify, ...(bootstrap !== undefined ? { bootstrap } : {}), ...(options.host ? { host: options.host } : {}) });\n this.listening = new Promise((resolve) => this.dht.listen(0, options.host, () => resolve()));\n // A node of the table that errors must not end the process.\n this.dht.on(\"error\", () => {});\n }\n\n /** Adds a node (a testnet's peers). */\n addNode(host: string, port: number): void { this.dht.addNode({ host, port }); }\n address(): { port: number } { return this.dht.address() as { port: number }; }\n\n /** Puts a relay payload (`<signature><timestamp><DNS packet>`) as the mutable item of `pubKeyZ32`; resolves with the nodes that took it. */\n put(pubKeyZ32: string, payload: Uint8Array): Promise<number> {\n const k = Buffer.from(publicKeyFromZ32(pubKeyZ32));\n const sig = Buffer.from(payload.subarray(0, SIGNATURE));\n const seq = Number(new DataView(payload.buffer, payload.byteOffset + SIGNATURE, TIMESTAMP).getBigUint64(0));\n const v = Buffer.from(payload.subarray(SIGNATURE + TIMESTAMP));\n return this.bounded(new Promise((resolve, reject) => {\n this.dht.put({ k, v, seq, sig }, (error, _hash, nodes) => {\n if (error) reject(error); else if (!nodes) reject(new Error(\"No DHT node took the packet\")); else resolve(nodes);\n });\n }));\n }\n\n /** The newest packet of `pubKeyZ32` the DHT holds, checked; null when nobody has one. */\n get(pubKeyZ32: string): Promise<SignedPacket | null> {\n const k = Buffer.from(publicKeyFromZ32(pubKeyZ32));\n // A mutable item without salt is found under the SHA-1 of its public key (BEP44).\n const target = createHash(\"sha1\").update(k).digest();\n return this.bounded(new Promise((resolve, reject) => {\n this.dht.get(target, { cache: false }, (error, found) => {\n if (error) return reject(error);\n if (!found?.v || !found.sig) return resolve(null);\n const timestamp = new Uint8Array(TIMESTAMP);\n new DataView(timestamp.buffer).setBigUint64(0, BigInt(found.seq));\n const payload = new Uint8Array(SIGNATURE + TIMESTAMP + found.v.length);\n payload.set(found.sig, 0); payload.set(timestamp, SIGNATURE); payload.set(found.v, SIGNATURE + TIMESTAMP);\n try { resolve(parseRelayPayload(pubKeyZ32, payload)); } catch { resolve(null); }\n });\n }));\n }\n\n private bounded<T>(work: Promise<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(\"DHT lookup timed out\")), this.timeoutMs);\n work.then((value) => { clearTimeout(timer); resolve(value); }, (error) => { clearTimeout(timer); reject(error); });\n });\n }\n\n destroy(): Promise<void> { return new Promise((resolve) => this.dht.destroy(() => resolve())); }\n}\n\n/**\n * The relays first, as the web app reaches Pkarr (its contacts read only the relays), and the DHT beside them: every\n * packet also goes to the DHT, and a read goes there when every relay is failing or the read failed for anything but\n * this app's own request budget (that is a wait, not an outage).\n *\n * `pinned`: the relays given are the only ones (`GHOSTLY_PKARR_RELAYS`, a private network): the profile's `relays`\n * setting does not replace them. With no DHT (`dht` null), the relays alone carry every packet.\n */\nexport class RelaysAndDht implements PkarrTransport {\n readonly relays: RelayTransport;\n private lastVia: \"relay\" | \"dht\" = \"relay\";\n\n constructor(private readonly dht: Mainline | null, relays: RelayTransportOptions = {}, private readonly pinned = false) {\n this.relays = new RelayTransport(relays);\n }\n\n async publish(identity: Identity, records: GhostRecord[], options: PkarrRequestOptions = {}): Promise<void> {\n // BEP44 sequence numbers must strictly increase, on the relays and on the DHT alike: one payload for both, dated\n // past every packet put or read under the key (`RelayTransport.publishDated`), whichever clock dated those.\n await this.relays.publishDated(identity, records, (key, payload, _timestamp, conflict) => this.putBoth(key, payload, options, conflict), options);\n }\n\n publishPayload(pubKeyZ32: string, payload: Uint8Array, options: PkarrRequestOptions = {}): Promise<void> {\n return this.putBoth(pubKeyZ32, payload, options);\n }\n\n private async putBoth(pubKeyZ32: string, payload: Uint8Array, options: PkarrRequestOptions, conflict?: (relay: string) => void): Promise<void> {\n if (!this.dht) return this.relays.publishPayload(pubKeyZ32, payload, options, conflict);\n const onDht = this.dht.put(pubKeyZ32, payload);\n onDht.catch(() => {});\n try {\n await this.relays.publishPayload(pubKeyZ32, payload, options, conflict);\n } catch (error) {\n // Held back by this app's budget: the relays take it when it frees (browser contacts read only them).\n if (isDiscoveryBudgetError(error)) throw error;\n // The relays failed: out on the DHT is out (a Pkarr relay reads the DHT for a key it has not got).\n await onDht.catch(() => { throw error; });\n }\n }\n\n async resolve(pubKeyZ32: string, options: PkarrRequestOptions = {}): Promise<SignedPacket | null> {\n if (!this.dht) return this.relays.resolve(pubKeyZ32, options);\n let fromRelays: SignedPacket | null = null, failed: unknown = null;\n // Every relay left alone for failing: the relays answer from what they last saw, if anything; the DHT is asked.\n const down = () => this.relays.discovery().relays.every((relay) => relay.state === \"failing\");\n if (!down()) {\n try { fromRelays = await this.relays.resolve(pubKeyZ32, options); this.lastVia = \"relay\"; } catch (error) {\n if (isDiscoveryBudgetError(error)) throw error;\n failed = error;\n }\n if (!failed && !down()) return fromRelays;\n }\n try {\n const fromDht = await this.dht.get(pubKeyZ32);\n this.lastVia = \"dht\";\n if (fromDht) this.relays.sawTimestamp(pubKeyZ32, fromDht.timestampMicros);\n return newerPacket(fromRelays, fromDht);\n } catch (error) {\n if (fromRelays) return fromRelays;\n throw failed ?? error;\n }\n }\n\n describe(): { protocol: string; relays: string[] } {\n return { protocol: this.dht ? \"Pkarr relays (HTTP), and the Mainline DHT (BEP44) directly\" : \"Pkarr relays (HTTP)\", relays: this.relays.describe().relays };\n }\n discovery(): DiscoveryStatus {\n const status = this.relays.discovery();\n return this.lastVia === \"dht\" ? { ...status, path: { via: \"dht\" } } : status;\n }\n subscribe(listener: Parameters<RelayTransport[\"subscribe\"]>[0]): () => void { return this.relays.subscribe(listener); }\n /** A relay's answer only: a packet the DHT hands back when the relays are down may be older than one already read. */\n readAnsweredAt(pubKeyZ32: string): number | undefined { return this.relays.readAnsweredAt(pubKeyZ32); }\n onServerTime(listener: Parameters<RelayTransport[\"onServerTime\"]>[0]): () => void { return this.relays.onServerTime(listener); }\n configure({ relays }: { relays: string[]; readRelays: boolean }): void { if (!this.pinned) this.relays.setRelays(relays); }\n}\n"],"mappings":"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"}