feat: add real-time streaming LDK Node log viewer
This commit is contained in:
@@ -4,6 +4,7 @@ android/local.properties
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
__MACOSX/
|
||||
|
||||
# Xcode
|
||||
#
|
||||
|
||||
@@ -168,6 +168,7 @@ import RapidGossipSync from './views/Settings/EmbeddedNode/RapidGossipSync';
|
||||
import EsploraServer from './views/Settings/EmbeddedNode/EsploraServer';
|
||||
import VssServer from './views/Settings/EmbeddedNode/VssServer';
|
||||
import LNDLogs from './views/Settings/EmbeddedNode/LNDLogs';
|
||||
import LDKLogs from './views/Settings/EmbeddedNode/LDKLogs';
|
||||
import Peers from './views/Settings/EmbeddedNode/Peers';
|
||||
import NeutrinoPeers from './views/Settings/EmbeddedNode/Peers/NeutrinoPeers';
|
||||
import ZeroConfPeers from './views/Settings/EmbeddedNode/Peers/ZeroConfPeers';
|
||||
@@ -1058,6 +1059,10 @@ export default class App extends React.PureComponent {
|
||||
name="LNDLogs" // @ts-ignore:next-line
|
||||
component={LNDLogs}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="LDKLogs" // @ts-ignore:next-line
|
||||
component={LDKLogs}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Peers" // @ts-ignore:next-line
|
||||
component={Peers}
|
||||
|
||||
@@ -13,6 +13,7 @@ class LdkNodeModule(reactContext: ReactApplicationContext) : ReactContextBaseJav
|
||||
|
||||
private var builder: Builder? = null
|
||||
private var node: Node? = null
|
||||
private var logFileObserver: LogFileObserver? = null
|
||||
|
||||
// Stored config values for building with custom Config
|
||||
private var storedNetwork: Network = Network.BITCOIN
|
||||
@@ -397,6 +398,12 @@ class LdkNodeModule(reactContext: ReactApplicationContext) : ReactContextBaseJav
|
||||
builder.setLiquiditySourceLsps7(nodeId, address, this.storedLsps7Token)
|
||||
}
|
||||
}
|
||||
|
||||
// Enable filesystem logging
|
||||
if (this.storedStorageDirPath.isNotEmpty()) {
|
||||
Log.d("LdkNodeModule", "applyBuilderSettings: Enabling filesystem logger")
|
||||
builder.setFilesystemLogger("${this.storedStorageDirPath}/ldk_node.log", LogLevel.DEBUG)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEsploraSyncConfig(): EsploraSyncConfig {
|
||||
@@ -434,6 +441,8 @@ class LdkNodeModule(reactContext: ReactApplicationContext) : ReactContextBaseJav
|
||||
try {
|
||||
val node = this@LdkNodeModule.node ?: throw Exception("Node not initialized")
|
||||
node.stop()
|
||||
this@LdkNodeModule.logFileObserver?.stopObserving()
|
||||
this@LdkNodeModule.logFileObserver = null
|
||||
this@LdkNodeModule.node = null
|
||||
withContext(Dispatchers.Main) {
|
||||
promise.resolve(null)
|
||||
@@ -1522,4 +1531,28 @@ class LdkNodeModule(reactContext: ReactApplicationContext) : ReactContextBaseJav
|
||||
promise.reject("error", e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Log File Methods
|
||||
|
||||
@ReactMethod
|
||||
fun tailLdkNodeLog(numLines: Int, promise: Promise) {
|
||||
val logPath = "${this.storedStorageDirPath}/ldk_node.log"
|
||||
promise.resolve(LogFileObserver.tailFile(logPath, numLines))
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun observeLdkNodeLogFile(promise: Promise) {
|
||||
if (logFileObserver != null) {
|
||||
promise.resolve(true)
|
||||
return
|
||||
}
|
||||
val logPath = "${this.storedStorageDirPath}/ldk_node.log"
|
||||
logFileObserver = LogFileObserver(logPath) { line ->
|
||||
reactApplicationContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit("ldklog", line + "\n")
|
||||
}
|
||||
logFileObserver?.startObserving()
|
||||
promise.resolve(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.lightningdevkit.ldknode
|
||||
|
||||
import android.os.FileObserver
|
||||
import android.util.Log
|
||||
import java.io.*
|
||||
|
||||
class LogFileObserver(
|
||||
private val filePath: String,
|
||||
private val onNewLine: (String) -> Unit
|
||||
) {
|
||||
private var fileObserver: FileObserver? = null
|
||||
private var reader: BufferedReader? = null
|
||||
|
||||
fun startObserving() {
|
||||
// Open file, create if needed
|
||||
val file = File(filePath)
|
||||
file.parentFile?.mkdirs()
|
||||
if (!file.exists()) file.createNewFile()
|
||||
|
||||
val stream = FileInputStream(file)
|
||||
reader = BufferedReader(InputStreamReader(stream))
|
||||
|
||||
// Read to EOF without emitting (skip historical)
|
||||
readToEnd(emit = false)
|
||||
|
||||
// Watch for MODIFY events
|
||||
fileObserver = object : FileObserver(filePath) {
|
||||
override fun onEvent(event: Int, path: String?) {
|
||||
if (event != MODIFY) return
|
||||
readToEnd(emit = true)
|
||||
}
|
||||
}
|
||||
fileObserver?.startWatching()
|
||||
}
|
||||
|
||||
fun stopObserving() {
|
||||
fileObserver?.stopWatching()
|
||||
fileObserver = null
|
||||
reader?.close()
|
||||
reader = null
|
||||
}
|
||||
|
||||
private fun readToEnd(emit: Boolean) {
|
||||
try {
|
||||
var line = reader?.readLine()
|
||||
while (line != null) {
|
||||
if (emit) onNewLine(line)
|
||||
line = reader?.readLine()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("LogFileObserver", "Error reading from log file", e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun tailFile(filePath: String, numLines: Int): String {
|
||||
val file = File(filePath)
|
||||
if (!file.exists() || file.length() == 0L) return ""
|
||||
|
||||
// Read last chunk (256KB should be plenty for typical numLines)
|
||||
val chunkSize = minOf(262144L, file.length())
|
||||
val raf = RandomAccessFile(file, "r")
|
||||
raf.use {
|
||||
it.seek(file.length() - chunkSize)
|
||||
val bytes = ByteArray(chunkSize.toInt())
|
||||
it.readFully(bytes)
|
||||
val content = String(bytes, Charsets.UTF_8)
|
||||
val lines = content.split("\n")
|
||||
return lines.takeLast(numLines).joinToString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -12,9 +12,9 @@ ANDROID_LINK=$FILE_PATH$ANDROID_FILE
|
||||
IOS_LINK=$FILE_PATH$IOS_FILE.zip
|
||||
|
||||
# LDK Node
|
||||
LDK_NODE_VERSION=v0.7.0-zeus-lsps7-rgs-troubleshoot
|
||||
LDK_NODE_VERSION=v0.7.0-zeus-lsps7-rgs-troubleshoot-logobserver
|
||||
LDK_NODE_IOS_FILE=LDKNodeFFI.xcframework
|
||||
LDK_NODE_IOS_SHA256='a99cf0cfd623737e588c48ae8f97bab4c71b18ce9593bb60a4a7cabcaf0d9026'
|
||||
LDK_NODE_IOS_SHA256='75b031ebb05d186f0ffb09c336097a6f7b30205cb26596551b054bc07ea67907'
|
||||
LDK_NODE_IOS_LINK=https://github.com/ZeusLN/ldk-node/releases/download/$LDK_NODE_VERSION/$LDK_NODE_IOS_FILE.zip
|
||||
|
||||
# test that curl and unzip are installed
|
||||
|
||||
@@ -78,6 +78,16 @@ RCT_EXTERN_METHOD(eventHandled:(RCTPromiseResolveBlock)resolve rejecter:(RCTProm
|
||||
RCT_EXTERN_METHOD(lsps1RequestChannel:(double)lspBalanceSat clientBalanceSat:(double)clientBalanceSat channelExpiryBlocks:(double)channelExpiryBlocks announceChannel:(BOOL)announceChannel resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(lsps1CheckOrderStatus:(NSString *)orderId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
|
||||
// LSPS7 Methods
|
||||
RCT_EXTERN_METHOD(setLiquiditySourceLsps7:(NSString *)nodeId address:(NSString *)address token:(NSString *)token resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(lsps7GetExtendableChannels:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(lsps7CreateOrder:(NSString *)shortChannelId channelExtensionExpiryBlocks:(double)channelExtensionExpiryBlocks token:(NSString *)token refundOnchainAddress:(NSString *)refundOnchainAddress resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(lsps7CheckOrderStatus:(NSString *)orderId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
|
||||
// Log File Methods
|
||||
RCT_EXTERN_METHOD(tailLdkNodeLog:(nonnull NSNumber *)numLines resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(observeLdkNodeLogFile:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
|
||||
// Message Signing Methods
|
||||
RCT_EXTERN_METHOD(signMessage:(NSString *)message resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(verifySignature:(NSString *)message signature:(NSString *)signature publicKey:(NSString *)publicKey resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
|
||||
@@ -6,6 +6,7 @@ class LdkNodeModule: RCTEventEmitter {
|
||||
|
||||
private var node: Node?
|
||||
private var builder: Builder?
|
||||
private var logFileObserver: LogFileObserver?
|
||||
|
||||
// Stored config values for building with custom Config
|
||||
private var storedNetwork: Network = .bitcoin
|
||||
@@ -37,7 +38,7 @@ class LdkNodeModule: RCTEventEmitter {
|
||||
}
|
||||
|
||||
override func supportedEvents() -> [String]! {
|
||||
return ["LdkNodeEvent"]
|
||||
return ["LdkNodeEvent", "ldklog"]
|
||||
}
|
||||
|
||||
@objc
|
||||
@@ -381,6 +382,11 @@ class LdkNodeModule: RCTEventEmitter {
|
||||
if let lsps7NodeId = self.storedLsps7NodeId, let lsps7Address = self.storedLsps7Address {
|
||||
builder.setLiquiditySourceLsps7(nodeId: lsps7NodeId, address: lsps7Address, token: self.storedLsps7Token)
|
||||
}
|
||||
|
||||
if !self.storedStorageDirPath.isEmpty {
|
||||
NSLog("LdkNodeModule: applyBuilderSettings: Enabling filesystem logger")
|
||||
builder.setFilesystemLogger(logFilePath: "\(self.storedStorageDirPath)/ldk_node.log", maxLogLevel: .debug)
|
||||
}
|
||||
}
|
||||
|
||||
private func createEsploraSyncConfig() -> EsploraSyncConfig {
|
||||
@@ -420,6 +426,8 @@ class LdkNodeModule: RCTEventEmitter {
|
||||
|
||||
do {
|
||||
try node.stop()
|
||||
self.logFileObserver?.stopObserving()
|
||||
self.logFileObserver = nil
|
||||
self.node = nil
|
||||
resolve(["status": "ok"])
|
||||
} catch {
|
||||
@@ -1490,4 +1498,72 @@ class LdkNodeModule: RCTEventEmitter {
|
||||
return "refunded"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - LSPS7 Serialization Helpers
|
||||
|
||||
private func serializeLsps7OrderResponse(_ response: Lsps7OrderResponse) -> [String: Any] {
|
||||
var result: [String: Any] = [
|
||||
"orderId": response.orderId,
|
||||
"orderState": serializeLsps7OrderState(response.orderState),
|
||||
"channelExtensionExpiryBlocks": response.channelExtensionExpiryBlocks,
|
||||
"newChannelExpiryBlock": response.newChannelExpiryBlock,
|
||||
"paymentInfo": serializeLsps1PaymentInfo(response.payment),
|
||||
"channel": serializeLsps7ExtendableChannel(response.channel)
|
||||
]
|
||||
return result
|
||||
}
|
||||
|
||||
private func serializeLsps7ExtendableChannel(_ channel: Lsps7ExtendableChannel) -> [String: Any] {
|
||||
var result: [String: Any] = [
|
||||
"shortChannelId": channel.shortChannelId,
|
||||
"maxChannelExtensionExpiryBlocks": channel.maxChannelExtensionExpiryBlocks,
|
||||
"expirationBlock": channel.expirationBlock
|
||||
]
|
||||
if let originalOrder = channel.originalOrder {
|
||||
result["originalOrder"] = [
|
||||
"id": originalOrder.id,
|
||||
"service": originalOrder.service
|
||||
]
|
||||
}
|
||||
if let extensionOrderIds = channel.extensionOrderIds {
|
||||
result["extensionOrderIds"] = extensionOrderIds
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Log File Methods
|
||||
|
||||
@objc(tailLdkNodeLog:resolver:rejecter:)
|
||||
func tailLdkNodeLog(_ numLines: NSNumber,
|
||||
resolver resolve: @escaping RCTPromiseResolveBlock,
|
||||
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
||||
let logPath = "\(self.storedStorageDirPath)/ldk_node.log"
|
||||
resolve(LogFileObserver.tailFile(path: logPath, numLines: numLines.intValue))
|
||||
}
|
||||
|
||||
@objc(observeLdkNodeLogFile:rejecter:)
|
||||
func observeLdkNodeLogFile(_ resolve: @escaping RCTPromiseResolveBlock,
|
||||
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
||||
if logFileObserver != nil {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
let logPath = "\(self.storedStorageDirPath)/ldk_node.log"
|
||||
logFileObserver = LogFileObserver(filePath: logPath) { [weak self] data in
|
||||
self?.sendEvent(withName: "ldklog", body: data)
|
||||
}
|
||||
logFileObserver?.startObserving()
|
||||
resolve(true)
|
||||
}
|
||||
|
||||
private func serializeLsps7OrderState(_ state: Lsps7OrderState) -> String {
|
||||
switch state {
|
||||
case .created:
|
||||
return "CREATED"
|
||||
case .completed:
|
||||
return "COMPLETED"
|
||||
case .failed:
|
||||
return "FAILED"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import Foundation
|
||||
|
||||
public class LogFileObserver {
|
||||
private let filePath: String
|
||||
private let onNewData: (String) -> Void
|
||||
private var fileHandle: FileHandle?
|
||||
private var source: DispatchSourceFileSystemObject?
|
||||
|
||||
public init(filePath: String, onNewData: @escaping (String) -> Void) {
|
||||
self.filePath = filePath
|
||||
self.onNewData = onNewData
|
||||
}
|
||||
|
||||
public func startObserving() {
|
||||
// Create file if needed
|
||||
let fm = FileManager.default
|
||||
if !fm.fileExists(atPath: filePath) {
|
||||
let dir = (filePath as NSString).deletingLastPathComponent
|
||||
try? fm.createDirectory(atPath: dir, withIntermediateDirectories: true)
|
||||
fm.createFile(atPath: filePath, contents: nil)
|
||||
}
|
||||
|
||||
guard let fh = FileHandle(forReadingAtPath: filePath) else { return }
|
||||
self.fileHandle = fh
|
||||
|
||||
// Seek to end (skip historical)
|
||||
fh.seekToEndOfFile()
|
||||
|
||||
// Use GCD dispatch source to watch for writes
|
||||
let fd = fh.fileDescriptor
|
||||
let source = DispatchSource.makeFileSystemObjectSource(
|
||||
fileDescriptor: fd,
|
||||
eventMask: .write,
|
||||
queue: DispatchQueue.global(qos: .utility)
|
||||
)
|
||||
source.setEventHandler { [weak self] in
|
||||
let data = fh.availableData
|
||||
if data.count > 0, let str = String(data: data, encoding: .utf8) {
|
||||
self?.onNewData(str)
|
||||
}
|
||||
}
|
||||
source.setCancelHandler {
|
||||
fh.closeFile()
|
||||
}
|
||||
source.resume()
|
||||
self.source = source
|
||||
}
|
||||
|
||||
public func stopObserving() {
|
||||
source?.cancel()
|
||||
source = nil
|
||||
fileHandle = nil
|
||||
}
|
||||
|
||||
public static func tailFile(path: String, numLines: Int) -> String {
|
||||
guard let fh = FileHandle(forReadingAtPath: path) else { return "" }
|
||||
defer { fh.closeFile() }
|
||||
|
||||
let fileSize = fh.seekToEndOfFile()
|
||||
if fileSize == 0 { return "" }
|
||||
|
||||
// Read last chunk (256KB should be plenty for typical numLines)
|
||||
var chunkSize: UInt64 = 262144
|
||||
if chunkSize > fileSize { chunkSize = fileSize }
|
||||
fh.seek(toFileOffset: fileSize - chunkSize)
|
||||
|
||||
guard let data = String(data: fh.readDataToEndOfFile(), encoding: .utf8) else { return "" }
|
||||
let lines = data.components(separatedBy: "\n")
|
||||
return lines.suffix(numLines).joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,7 @@
|
||||
B1C2D3E62D36C99900000003 /* LDKNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C2D3E52D36C99900000002 /* LDKNode.swift */; };
|
||||
B1C2D3E92D36C99900000006 /* LdkNodeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C2D3E72D36C99900000004 /* LdkNodeModule.swift */; };
|
||||
B1C2D3EA2D36C99900000007 /* LdkNodeModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B1C2D3E82D36C99900000005 /* LdkNodeModule.m */; };
|
||||
B1C2D3EC2D36C99900000009 /* LogFileObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C2D3EB2D36C99900000008 /* LogFileObserver.swift */; };
|
||||
B1DB19555C1C4546922217C2 /* eye_closed.svg in Resources */ = {isa = PBXBuildFile; fileRef = 744BE042F00E4B16BDC79870 /* eye_closed.svg */; };
|
||||
B20A92EA4E82493CB6D1A2F0 /* NFC.svg in Resources */ = {isa = PBXBuildFile; fileRef = 3C37D42473A54AD981B549B4 /* NFC.svg */; };
|
||||
B39739CDBF39F16F2AD52938 /* libPods-zeus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EC14DF216D694B9C5C997FE /* libPods-zeus.a */; };
|
||||
@@ -676,6 +677,7 @@
|
||||
B1C2D3E52D36C99900000002 /* LDKNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LDKNode.swift; sourceTree = "<group>"; };
|
||||
B1C2D3E72D36C99900000004 /* LdkNodeModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LdkNodeModule.swift; sourceTree = "<group>"; };
|
||||
B1C2D3E82D36C99900000005 /* LdkNodeModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LdkNodeModule.m; sourceTree = "<group>"; };
|
||||
B1C2D3EB2D36C99900000008 /* LogFileObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFileObserver.swift; sourceTree = "<group>"; };
|
||||
B36D434AE9B14EE7AD5E6BCE /* Routing.svg */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Routing.svg; path = ../assets/images/SVG/Routing.svg; sourceTree = "<group>"; };
|
||||
B4C39762C9B748E39BFDA90F /* ExchangeBitcoin.svg */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = ExchangeBitcoin.svg; path = ../assets/images/SVG/ExchangeBitcoin.svg; sourceTree = "<group>"; };
|
||||
B4D00602022C4AE1BFA1A2C1 /* Hourglass.svg */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Hourglass.svg; path = ../assets/images/SVG/Hourglass.svg; sourceTree = "<group>"; };
|
||||
@@ -1097,6 +1099,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B1C2D3E52D36C99900000002 /* LDKNode.swift */,
|
||||
B1C2D3EB2D36C99900000008 /* LogFileObserver.swift */,
|
||||
B1C2D3E72D36C99900000004 /* LdkNodeModule.swift */,
|
||||
B1C2D3E82D36C99900000005 /* LdkNodeModule.m */,
|
||||
);
|
||||
@@ -2038,6 +2041,7 @@
|
||||
B1C2D3E62D36C99900000003 /* LDKNode.swift in Sources */,
|
||||
B1C2D3E92D36C99900000006 /* LdkNodeModule.swift in Sources */,
|
||||
B1C2D3EA2D36C99900000007 /* LdkNodeModule.m in Sources */,
|
||||
B1C2D3EC2D36C99900000009 /* LogFileObserver.swift in Sources */,
|
||||
745388452A54C2B6000927CE /* LndMobileScheduledSync.swift in Sources */,
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
|
||||
13B07FBD1A6810BG00A75B9A /* BackgroundTaskManager.m in Sources */,
|
||||
|
||||
Vendored
+19
@@ -518,6 +518,25 @@ export interface ILdkNodeModule {
|
||||
): Promise<Lsps1OrderResponse>;
|
||||
lsps1CheckOrderStatus(orderId: string): Promise<Lsps1OrderStatus>;
|
||||
|
||||
// LSPS7 Methods
|
||||
setLiquiditySourceLsps7(
|
||||
nodeId: string,
|
||||
address: string,
|
||||
token?: string | null
|
||||
): Promise<void>;
|
||||
lsps7GetExtendableChannels(): Promise<Lsps7ExtendableChannel[]>;
|
||||
lsps7CreateOrder(
|
||||
shortChannelId: string,
|
||||
channelExtensionExpiryBlocks: number,
|
||||
token?: string | null,
|
||||
refundOnchainAddress?: string | null
|
||||
): Promise<Lsps7OrderResponse>;
|
||||
lsps7CheckOrderStatus(orderId: string): Promise<Lsps7OrderResponse>;
|
||||
|
||||
// Log File Methods
|
||||
tailLdkNodeLog(numLines: number): Promise<string>;
|
||||
observeLdkNodeLogFile(): Promise<boolean>;
|
||||
|
||||
// Message Signing Methods
|
||||
signMessage(message: string): Promise<{ signature: string }>;
|
||||
verifySignature(
|
||||
|
||||
@@ -1291,6 +1291,7 @@
|
||||
"views.Settings.EmbeddedNode.AdvancedRescan.start": "Start rescan",
|
||||
"views.Settings.EmbeddedNode.LNDLogs.title": "LND Logs",
|
||||
"views.Settings.EmbeddedNode.LNDLogs.copyLogs": "Copy logs to clipboard",
|
||||
"views.Settings.EmbeddedNode.LDKLogs.title": "LDK Node Logs",
|
||||
"views.Settings.EmbeddedNode.feeEstimator": "Fee estimator",
|
||||
"views.Settings.EmbeddedNode.customFeeEstimator": "Custom fee estimator",
|
||||
"views.Settings.EmbeddedNode.speedloader": "Express Graph Sync server",
|
||||
|
||||
@@ -16,3 +16,9 @@ export const LndMobileToolsEventEmitter =
|
||||
? DeviceEventEmitter
|
||||
: // @ts-ignore:next-line
|
||||
new NativeEventEmitter(NativeModules.LndMobileTools);
|
||||
|
||||
export const LdkNodeEventEmitter =
|
||||
Platform.OS == 'android'
|
||||
? DeviceEventEmitter
|
||||
: // @ts-ignore:next-line
|
||||
new NativeEventEmitter(NativeModules.LdkNodeModule);
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import * as React from 'react';
|
||||
import { NativeModules, Platform, View } from 'react-native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import CopyButton from '../../../components/CopyButton';
|
||||
import Screen from '../../../components/Screen';
|
||||
import Header from '../../../components/Header';
|
||||
import LogBox from '../../../components/LogBox';
|
||||
|
||||
import { localeString } from '../../../utils/LocaleUtils';
|
||||
import { themeColor } from '../../../utils/ThemeUtils';
|
||||
import { LdkNodeEventEmitter } from '../../../utils/EventListenerUtils';
|
||||
|
||||
const MAX_LOG_LENGTH = 100000;
|
||||
|
||||
interface LDKLogsProps {
|
||||
navigation: NativeStackNavigationProp<any, any>;
|
||||
}
|
||||
|
||||
interface LDKLogsState {
|
||||
log: string;
|
||||
}
|
||||
|
||||
export default class LDKLogs extends React.Component<
|
||||
LDKLogsProps,
|
||||
LDKLogsState
|
||||
> {
|
||||
state = {
|
||||
log: ''
|
||||
};
|
||||
logListener: any = null;
|
||||
|
||||
async componentDidMount(): Promise<void> {
|
||||
(async () => {
|
||||
const tailLog = await NativeModules.LdkNodeModule.tailLdkNodeLog(
|
||||
100
|
||||
);
|
||||
let log = tailLog;
|
||||
|
||||
this.logListener = LdkNodeEventEmitter.addListener(
|
||||
'ldklog',
|
||||
(data: string) => {
|
||||
log = log + data;
|
||||
if (log.length > MAX_LOG_LENGTH) {
|
||||
log = log.slice(-MAX_LOG_LENGTH);
|
||||
}
|
||||
this.setState({ log });
|
||||
}
|
||||
);
|
||||
|
||||
NativeModules.LdkNodeModule.observeLdkNodeLogFile().catch(
|
||||
(e: any) => {
|
||||
console.log('Could not observe LDK log file:', e);
|
||||
}
|
||||
);
|
||||
|
||||
this.setState({ log });
|
||||
})();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.logListener) {
|
||||
this.logListener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { log } = this.state;
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Header
|
||||
leftComponent="Back"
|
||||
centerComponent={{
|
||||
text: localeString(
|
||||
'views.Settings.EmbeddedNode.LDKLogs.title'
|
||||
),
|
||||
style: {
|
||||
color: themeColor('text'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}
|
||||
}}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<LogBox text={log} scrollLock={false} />
|
||||
<View
|
||||
style={{
|
||||
marginBottom: Platform.OS === 'android' ? 20 : 0
|
||||
}}
|
||||
>
|
||||
<CopyButton
|
||||
title={localeString(
|
||||
'views.Settings.EmbeddedNode.LNDLogs.copyLogs'
|
||||
)}
|
||||
copyValue={log}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,9 @@ export default class LNDLogs extends React.Component<
|
||||
'lndlog',
|
||||
(data: string) => {
|
||||
log = log + data.slice(11);
|
||||
if (log.length > 100000) {
|
||||
log = log.slice(-100000);
|
||||
}
|
||||
this.setState({
|
||||
log
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ScrollView, Text, View } from 'react-native';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import Button from '../../../components/Button';
|
||||
import Header from '../../../components/Header';
|
||||
@@ -16,7 +16,7 @@ import { themeColor } from '../../../utils/ThemeUtils';
|
||||
import { getDefaultRgsServer } from '../../../utils/EmbeddedLdkNodeUtils';
|
||||
|
||||
interface RapidGossipSyncProps {
|
||||
navigation: StackNavigationProp<any, any>;
|
||||
navigation: NativeStackNavigationProp<any, any>;
|
||||
SettingsStore: SettingsStore;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ScrollView, Text, View } from 'react-native';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import Button from '../../../components/Button';
|
||||
import Header from '../../../components/Header';
|
||||
@@ -16,7 +16,7 @@ import { themeColor } from '../../../utils/ThemeUtils';
|
||||
import { DEFAULT_VSS_SERVER } from '../../../utils/EmbeddedLdkNodeUtils';
|
||||
|
||||
interface VssServerProps {
|
||||
navigation: StackNavigationProp<any, any>;
|
||||
navigation: NativeStackNavigationProp<any, any>;
|
||||
SettingsStore: SettingsStore;
|
||||
}
|
||||
|
||||
|
||||
@@ -273,6 +273,33 @@ export default class EmbeddedNode extends React.Component<
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
{/* Embedded LDK: LDK Logs */}
|
||||
{isEmbeddedLdk && (
|
||||
<ListItem
|
||||
containerStyle={{
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
onPress={() => navigation.navigate('LDKLogs')}
|
||||
>
|
||||
<ListItem.Content>
|
||||
<ListItem.Title
|
||||
style={{
|
||||
color: themeColor('text'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.EmbeddedNode.LDKLogs.title'
|
||||
)}
|
||||
</ListItem.Title>
|
||||
</ListItem.Content>
|
||||
<Icon
|
||||
name="keyboard-arrow-right"
|
||||
color={themeColor('secondaryText')}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
{/* Embedded LND: Advanced */}
|
||||
{isEmbeddedLnd && (
|
||||
<ListItem
|
||||
|
||||
Reference in New Issue
Block a user