@@ -29,6 +29,7 @@ export type ProbeResult = {
2929 invoiceFetched : boolean ;
3030 success : boolean ;
3131 durationMs : number ;
32+ routeFeeMsat ?: number ;
3233 bolt11 ?: string ;
3334 nodeId ?: string ;
3435 rawProviderResult ?: string ;
@@ -49,6 +50,12 @@ type LnurlInvoiceResponse = {
4950 reason ?: string ;
5051} ;
5152
53+ export type ProbeCommandResult = {
54+ success : boolean ;
55+ durationMs ?: number ;
56+ routeFeeMsat ?: number ;
57+ } ;
58+
5259const DEFAULT_PROBE_TIMEOUT_SECONDS = 90 ;
5360const DEFAULT_PROBE_FETCH_RETRIES = 2 ;
5461const DEFAULT_PROBE_FETCH_RETRY_DELAY_MS = 1_000 ;
@@ -234,24 +241,56 @@ export function runProbeNodeCommand(target: ProbeTarget, amountMsat: number): st
234241 return runDevToolsCommand ( method , payload , timeoutSeconds ) ;
235242}
236243
237- export function parseProbeCommandSuccess ( raw : string ) : boolean {
244+ function parseDevResultPayload ( raw : string ) : Record < string , unknown > | null {
238245 const result = extractContentCallResult ( raw ) ;
239- if ( ! result ) return false ;
246+ if ( ! result ) return null ;
240247
241248 let parsed : unknown ;
242249 try {
243250 parsed = JSON . parse ( result ) ;
244251 } catch {
245- return false ;
252+ return null ;
246253 }
247- if ( typeof parsed !== 'object' || parsed === null ) return false ;
254+ if ( typeof parsed !== 'object' || parsed === null ) return null ;
255+
256+ return parsed as Record < string , unknown > ;
257+ }
258+
259+ function parseDevResultSuccess ( payload : Record < string , unknown > ) : boolean {
260+ if ( 'success' in payload ) return payload . success === true ;
261+
262+ const type = payload . type ;
263+ return (
264+ typeof type === 'string' && ( type === 'Success' || type . endsWith ( '.ProbeSuccess' ) )
265+ ) ;
266+ }
267+
268+ export function parseProbeCommandResult ( raw : string ) : ProbeCommandResult | null {
269+ const payload = parseDevResultPayload ( raw ) ;
270+ if ( ! payload ) return null ;
271+
272+ return {
273+ success : parseDevResultSuccess ( payload ) ,
274+ durationMs : parseOptionalNumber ( payload . durationMs ) ,
275+ routeFeeMsat : parseOptionalNumber ( payload . routeFeeMsat ) ,
276+ } ;
277+ }
248278
249- if ( 'success' in parsed ) return parsed . success === true ;
250- if ( 'type' in parsed && typeof parsed . type === 'string' ) {
251- return parsed . type === 'Success' || parsed . type . endsWith ( '.ProbeSuccess' ) ;
279+ export function parseProbeCommandSuccess ( raw : string ) : boolean {
280+ const payload = parseDevResultPayload ( raw ) ;
281+ return payload ? parseDevResultSuccess ( payload ) : false ;
282+ }
283+
284+ function parseOptionalNumber ( value : unknown ) : number | undefined {
285+ if ( typeof value === 'number' && Number . isFinite ( value ) ) {
286+ return value ;
287+ }
288+ if ( typeof value === 'string' ) {
289+ const parsedValue = Number . parseInt ( value , 10 ) ;
290+ return Number . isFinite ( parsedValue ) ? parsedValue : undefined ;
252291 }
253292
254- return false ;
293+ return undefined ;
255294}
256295
257296export function summarizeProbeCommandFailure ( raw : string ) : string {
@@ -299,10 +338,11 @@ export async function resetPathfindingScores({
299338 console . info ( `→ [${ logPrefix } ] Resetting pathfinding scores (timeout ${ timeoutSeconds } s)...` ) ;
300339 const fallbackFloorS = getDeviceEpochSeconds ( ) ;
301340 const raw = runDevToolsCommand ( method , { } , timeoutSeconds ) ;
302- if ( ! parseProbeCommandSuccess ( raw ) ) {
341+ const payload = parseDevResultPayload ( raw ) ;
342+ if ( ! payload || ! parseDevResultSuccess ( payload ) ) {
303343 throw new Error ( `Pathfinding scores reset failed: ${ summarizeProbeCommandFailure ( raw ) } ` ) ;
304344 }
305- const deviceResetAtS = parseResetTimestamp ( raw ) ;
345+ const deviceResetAtS = parseResetTimestamp ( payload ) ;
306346 if ( deviceResetAtS === null ) {
307347 console . warn (
308348 `→ [${ logPrefix } ] Reset result has no timestamp (old app build?); using pre-reset device time as scores sync floor`
@@ -313,20 +353,8 @@ export async function resetPathfindingScores({
313353 return resetFloorS ;
314354}
315355
316- function parseResetTimestamp ( raw : string ) : number | null {
317- const result = extractContentCallResult ( raw ) ;
318- if ( ! result ) return null ;
319-
320- let parsed : unknown ;
321- try {
322- parsed = JSON . parse ( result ) ;
323- } catch {
324- return null ;
325- }
326- if ( typeof parsed !== 'object' || parsed === null ) return null ;
327- if ( ! ( 'timestamp' in parsed ) ) return null ;
328-
329- const timestamp = parsed . timestamp ;
356+ function parseResetTimestamp ( payload : Record < string , unknown > ) : number | null {
357+ const timestamp = payload . timestamp ;
330358 return typeof timestamp === 'number' && Number . isFinite ( timestamp ) && timestamp > 0
331359 ? timestamp
332360 : null ;
@@ -557,8 +585,8 @@ export function renderProbeReport(
557585 `Scores reset: ${ scoresResetForReport ( ) } ` ,
558586 `Readiness at probe start: ${ readiness ? summarizeProbeReadiness ( readiness ) : 'not captured' } ` ,
559587 '' ,
560- '| Target | Type | Amount sats | Required | Fetch | Probe | Retries | Duration ms | Failure |' ,
561- '| --- | --- | ---: | --- | --- | --- | ---: | ---: | --- |' ,
588+ '| Target | Type | Amount sats | Required | Fetch | Probe | Retries | Duration ms | Route fee msat | Failure |' ,
589+ '| --- | --- | ---: | --- | --- | --- | ---: | ---: | ---: | --- |' ,
562590 ] ;
563591
564592 for ( const result of results ) {
@@ -572,6 +600,7 @@ export function renderProbeReport(
572600 result . success ? '✅' : '❌' ,
573601 result . retries . toString ( ) ,
574602 result . durationMs . toString ( ) ,
603+ formatRouteFeeCell ( result ) ,
575604 result . success ? '' : formatFailureCell ( result . error ?? '' ) ,
576605 ] . join ( ' | ' ) } |`
577606 ) ;
@@ -684,6 +713,9 @@ async function fetchJsonOnce<T>(url: string): Promise<T> {
684713 if ( ! response . ok ) {
685714 throw new Error ( `HTTP ${ response . status } for ${ url } ${ formatResponseBody ( text ) } ` ) ;
686715 }
716+ if ( text . trim ( ) . length === 0 ) {
717+ throw new Error ( `HTTP ${ response . status } for ${ url } returned an empty response body` ) ;
718+ }
687719
688720 return JSON . parse ( text ) as T ;
689721}
@@ -759,6 +791,10 @@ function formatFetchCell(result: ProbeResult): string {
759791 return result . invoiceFetched ? 'ok' : 'failed' ;
760792}
761793
794+ function formatRouteFeeCell ( result : ProbeResult ) : string {
795+ return result . routeFeeMsat === undefined ? '' : result . routeFeeMsat . toString ( ) ;
796+ }
797+
762798function runDevToolsCommand (
763799 method : string ,
764800 payload : Record < string , unknown > ,
0 commit comments