diff --git a/proto/wizard/wizard_structs.proto b/proto/wizard/wizard_structs.proto index e997a1b8..160eb02f 100644 --- a/proto/wizard/wizard_structs.proto +++ b/proto/wizard/wizard_structs.proto @@ -15,6 +15,7 @@ message ConfigRequest { string relay_url = 2; bool automate_liquidity = 3; bool push_backups_to_nostr = 4; + string avatar_url = 5; } message AdminConnectInfoResponse { string nprofile = 1; @@ -37,4 +38,10 @@ message ServiceStateResponse { bool watchdog_ok = 6; string http_url = 7; string nprofile = 8; + string source_name = 9; + string relay_url = 10; + bool automate_liquidity = 11; + bool push_backups_to_nostr = 12; + string avatar_url = 13; + string app_id = 14; } \ No newline at end of file diff --git a/proto/wizard_service/autogenerated/client.md b/proto/wizard_service/autogenerated/client.md index 69596785..c3d4e77c 100644 --- a/proto/wizard_service/autogenerated/client.md +++ b/proto/wizard_service/autogenerated/client.md @@ -24,20 +24,6 @@ The nostr server will send back a message response, and inside the body there wi ## HTTP Methods ### These are the http methods the client implements to communicate with the API -- WizardState - - auth type: __Guest__ - - http method: __get__ - - http route: __/wizard/state__ - - This methods has an __empty__ __request__ body - - output: [StateResponse](#StateResponse) - -- WizardConfig - - auth type: __Guest__ - - http method: __post__ - - http route: __/wizard/config__ - - input: [ConfigRequest](#ConfigRequest) - - This methods has an __empty__ __response__ body - - GetAdminConnectInfo - auth type: __Guest__ - http method: __get__ @@ -52,40 +38,61 @@ The nostr server will send back a message response, and inside the body there wi - This methods has an __empty__ __request__ body - output: [ServiceStateResponse](#ServiceStateResponse) +- WizardConfig + - auth type: __Guest__ + - http method: __post__ + - http route: __/wizard/config__ + - input: [ConfigRequest](#ConfigRequest) + - This methods has an __empty__ __response__ body + +- WizardState + - auth type: __Guest__ + - http method: __get__ + - http route: __/wizard/state__ + - This methods has an __empty__ __request__ body + - output: [StateResponse](#StateResponse) + # INPUTS AND OUTPUTS ## Messages ### The content of requests and response from the methods -### StateResponse - - __config_sent__: _boolean_ - - __admin_linked__: _boolean_ +### AdminConnectInfoResponse + - __connect_info__: _[AdminConnectInfoResponse_connect_info](#AdminConnectInfoResponse_connect_info)_ + - __nprofile__: _string_ ### ConfigRequest - - __source_name__: _string_ - - __relay_url__: _string_ - __automate_liquidity__: _boolean_ + - __avatar_url__: _string_ - __push_backups_to_nostr__: _boolean_ - -### AdminConnectInfoResponse - - __nprofile__: _string_ - - __connect_info__: _AdminConnectInfoResponse_connect_info_ - -### ServiceStateResponse - - __http_url__: _string_ - - __nprofile__: _string_ - - __provider_name__: _string_ - - __relays__: ARRAY of: _string_ - - __admin_npub__: _string_ - - __relay_connected__: _boolean_ - - __lnd_state__: _[LndState](#LndState)_ - - __watchdog_ok__: _boolean_ + - __relay_url__: _string_ + - __source_name__: _string_ ### Empty + +### ServiceStateResponse + - __admin_npub__: _string_ + - __app_id__: _string_ + - __automate_liquidity__: _boolean_ + - __avatar_url__: _string_ + - __http_url__: _string_ + - __lnd_state__: _[LndState](#LndState)_ + - __nprofile__: _string_ + - __provider_name__: _string_ + - __push_backups_to_nostr__: _boolean_ + - __relay_connected__: _boolean_ + - __relay_url__: _string_ + - __relays__: ARRAY of: _string_ + - __source_name__: _string_ + - __watchdog_ok__: _boolean_ + +### StateResponse + - __admin_linked__: _boolean_ + - __config_sent__: _boolean_ ## Enums ### The enumerators used in the messages ### LndState - __OFFLINE__ - - __SYNCING__ - __ONLINE__ + - __SYNCING__ diff --git a/proto/wizard_service/autogenerated/debug.txt b/proto/wizard_service/autogenerated/debug.txt index fd00e738..ca3d129c 100644 --- a/proto/wizard_service/autogenerated/debug.txt +++ b/proto/wizard_service/autogenerated/debug.txt @@ -1,61 +1,5 @@ ([]*main.Method) (len=4 cap=4) { - (*main.Method)(0xc00022a280)({ - in: (main.MethodMessage) { - name: (string) (len=5) "Empty", - hasZeroFields: (bool) true - }, - name: (string) (len=11) "WizardState", - out: (main.MethodMessage) { - name: (string) (len=13) "StateResponse", - hasZeroFields: (bool) false - }, - opts: (*main.methodOptions)(0xc00009a9c0)({ - authType: (*main.supportedAuth)(0xc0003c9aa0)({ - id: (string) (len=5) "guest", - name: (string) (len=5) "Guest", - context: (map[string]string) { - } - }), - method: (string) (len=3) "get", - route: (main.decodedRoute) { - route: (string) (len=13) "/wizard/state", - params: ([]string) - }, - query: ([]string) , - nostr: (bool) false, - batch: (bool) false - }), - serverStream: (bool) false - }), - (*main.Method)(0xc00022a2d0)({ - in: (main.MethodMessage) { - name: (string) (len=13) "ConfigRequest", - hasZeroFields: (bool) false - }, - name: (string) (len=12) "WizardConfig", - out: (main.MethodMessage) { - name: (string) (len=5) "Empty", - hasZeroFields: (bool) true - }, - opts: (*main.methodOptions)(0xc00009ab40)({ - authType: (*main.supportedAuth)(0xc0003c9b60)({ - id: (string) (len=5) "guest", - name: (string) (len=5) "Guest", - context: (map[string]string) { - } - }), - method: (string) (len=4) "post", - route: (main.decodedRoute) { - route: (string) (len=14) "/wizard/config", - params: ([]string) - }, - query: ([]string) , - nostr: (bool) false, - batch: (bool) false - }), - serverStream: (bool) false - }), - (*main.Method)(0xc00022a640)({ + (*main.Method)(0xc0002b00f0)({ in: (main.MethodMessage) { name: (string) (len=5) "Empty", hasZeroFields: (bool) true @@ -65,12 +9,11 @@ name: (string) (len=24) "AdminConnectInfoResponse", hasZeroFields: (bool) false }, - opts: (*main.methodOptions)(0xc00009acc0)({ - authType: (*main.supportedAuth)(0xc0003c9c20)({ + opts: (*main.methodOptions)(0xc0006907e0)({ + authType: (*main.supportedAuth)(0xc000347380)({ id: (string) (len=5) "guest", name: (string) (len=5) "Guest", - context: (map[string]string) { - } + context: ([]*main.authContext) }), method: (string) (len=3) "get", route: (main.decodedRoute) { @@ -83,7 +26,7 @@ }), serverStream: (bool) false }), - (*main.Method)(0xc00022a690)({ + (*main.Method)(0xc0002b0140)({ in: (main.MethodMessage) { name: (string) (len=5) "Empty", hasZeroFields: (bool) true @@ -93,12 +36,11 @@ name: (string) (len=20) "ServiceStateResponse", hasZeroFields: (bool) false }, - opts: (*main.methodOptions)(0xc00009ae40)({ - authType: (*main.supportedAuth)(0xc0003c9ce0)({ + opts: (*main.methodOptions)(0xc000690960)({ + authType: (*main.supportedAuth)(0xc000347440)({ id: (string) (len=5) "guest", name: (string) (len=5) "Guest", - context: (map[string]string) { - } + context: ([]*main.authContext) }), method: (string) (len=3) "get", route: (main.decodedRoute) { @@ -110,53 +52,101 @@ batch: (bool) false }), serverStream: (bool) false + }), + (*main.Method)(0xc0002b00a0)({ + in: (main.MethodMessage) { + name: (string) (len=13) "ConfigRequest", + hasZeroFields: (bool) false + }, + name: (string) (len=12) "WizardConfig", + out: (main.MethodMessage) { + name: (string) (len=5) "Empty", + hasZeroFields: (bool) true + }, + opts: (*main.methodOptions)(0xc000690660)({ + authType: (*main.supportedAuth)(0xc0003472c0)({ + id: (string) (len=5) "guest", + name: (string) (len=5) "Guest", + context: ([]*main.authContext) + }), + method: (string) (len=4) "post", + route: (main.decodedRoute) { + route: (string) (len=14) "/wizard/config", + params: ([]string) + }, + query: ([]string) , + nostr: (bool) false, + batch: (bool) false + }), + serverStream: (bool) false + }), + (*main.Method)(0xc0002b0050)({ + in: (main.MethodMessage) { + name: (string) (len=5) "Empty", + hasZeroFields: (bool) true + }, + name: (string) (len=11) "WizardState", + out: (main.MethodMessage) { + name: (string) (len=13) "StateResponse", + hasZeroFields: (bool) false + }, + opts: (*main.methodOptions)(0xc0006904e0)({ + authType: (*main.supportedAuth)(0xc000347200)({ + id: (string) (len=5) "guest", + name: (string) (len=5) "Guest", + context: ([]*main.authContext) + }), + method: (string) (len=3) "get", + route: (main.decodedRoute) { + route: (string) (len=13) "/wizard/state", + params: ([]string) + }, + query: ([]string) , + nostr: (bool) false, + batch: (bool) false + }), + serverStream: (bool) false }) } ([]*main.Enum) (len=1 cap=1) { - (*main.Enum)(0xc0003c9680)({ + (*main.Enum)(0xc000443d40)({ name: (string) (len=8) "LndState", values: ([]main.EnumValue) (len=3 cap=4) { (main.EnumValue) { number: (int64) 0, name: (string) (len=7) "OFFLINE" }, - (main.EnumValue) { - number: (int64) 1, - name: (string) (len=7) "SYNCING" - }, (main.EnumValue) { number: (int64) 2, name: (string) (len=6) "ONLINE" + }, + (main.EnumValue) { + number: (int64) 1, + name: (string) (len=7) "SYNCING" } } }) } -(map[string]*main.Message) (len=5) { - (string) (len=5) "Empty": (*main.Message)(0xc0003c94a0)({ - fullName: (string) (len=5) "Empty", - name: (string) (len=5) "Empty", - fields: (map[string]*main.Field) { - } - }), - (string) (len=13) "StateResponse": (*main.Message)(0xc0003c9500)({ - fullName: (string) (len=13) "StateResponse", - name: (string) (len=13) "StateResponse", - fields: (map[string]*main.Field) (len=2) { - (string) (len=11) "config_sent": (*main.Field)(0xc0003ee440)({ - name: (string) (len=11) "config_sent", - kind: (string) (len=4) "bool", +([]*main.SortableMessage) (len=5 cap=8) { + (*main.SortableMessage)(0xc0003475c0)({ + fullName: (string) (len=24) "AdminConnectInfoResponse", + name: (string) (len=24) "AdminConnectInfoResponse", + fields: ([]*main.Field) (len=2 cap=2) { + (*main.Field)(0xc000347480)({ + name: (string) (len=12) "connect_info", + kind: (string) (len=37) "AdminConnectInfoResponse_connect_info", isMap: (bool) false, isArray: (bool) false, isEnum: (bool) false, - isMessage: (bool) false, + isMessage: (bool) true, isOptional: (bool) false, - oneOfName: (string) "" + oneOfName: (string) (len=12) "connect_info" }), - (string) (len=12) "admin_linked": (*main.Field)(0xc0003ee480)({ - name: (string) (len=12) "admin_linked", - kind: (string) (len=4) "bool", + (*main.Field)(0xc000346bc0)({ + name: (string) (len=8) "nprofile", + kind: (string) (len=6) "string", isMap: (bool) false, isArray: (bool) false, isEnum: (bool) false, @@ -166,21 +156,11 @@ }) } }), - (string) (len=13) "ConfigRequest": (*main.Message)(0xc0003c9560)({ + (*main.SortableMessage)(0xc000347580)({ fullName: (string) (len=13) "ConfigRequest", name: (string) (len=13) "ConfigRequest", - fields: (map[string]*main.Field) (len=4) { - (string) (len=9) "relay_url": (*main.Field)(0xc0003ee500)({ - name: (string) (len=9) "relay_url", - kind: (string) (len=6) "string", - isMap: (bool) false, - isArray: (bool) false, - isEnum: (bool) false, - isMessage: (bool) false, - isOptional: (bool) false, - oneOfName: (string) "" - }), - (string) (len=18) "automate_liquidity": (*main.Field)(0xc0003ee540)({ + fields: ([]*main.Field) (len=5 cap=8) { + (*main.Field)(0xc000346b00)({ name: (string) (len=18) "automate_liquidity", kind: (string) (len=4) "bool", isMap: (bool) false, @@ -190,7 +170,17 @@ isOptional: (bool) false, oneOfName: (string) "" }), - (string) (len=21) "push_backups_to_nostr": (*main.Field)(0xc0003ee580)({ + (*main.Field)(0xc000346b80)({ + name: (string) (len=10) "avatar_url", + kind: (string) (len=6) "string", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346b40)({ name: (string) (len=21) "push_backups_to_nostr", kind: (string) (len=4) "bool", isMap: (bool) false, @@ -200,7 +190,17 @@ isOptional: (bool) false, oneOfName: (string) "" }), - (string) (len=11) "source_name": (*main.Field)(0xc0003ee4c0)({ + (*main.Field)(0xc000346ac0)({ + name: (string) (len=9) "relay_url", + kind: (string) (len=6) "string", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346a80)({ name: (string) (len=11) "source_name", kind: (string) (len=6) "string", isMap: (bool) false, @@ -212,37 +212,16 @@ }) } }), - (string) (len=24) "AdminConnectInfoResponse": (*main.Message)(0xc0003c95c0)({ - fullName: (string) (len=24) "AdminConnectInfoResponse", - name: (string) (len=24) "AdminConnectInfoResponse", - fields: (map[string]*main.Field) (len=2) { - (string) (len=8) "nprofile": (*main.Field)(0xc0003ee5c0)({ - name: (string) (len=8) "nprofile", - kind: (string) (len=6) "string", - isMap: (bool) false, - isArray: (bool) false, - isEnum: (bool) false, - isMessage: (bool) false, - isOptional: (bool) false, - oneOfName: (string) "" - }), - (string) (len=37) "AdminConnectInfoResponse_connect_info": (*main.Field)(0xc0003eea80)({ - name: (string) (len=12) "connect_info", - kind: (string) (len=37) "AdminConnectInfoResponse_connect_info", - isMap: (bool) false, - isArray: (bool) false, - isEnum: (bool) false, - isMessage: (bool) false, - isOptional: (bool) false, - oneOfName: (string) (len=12) "connect_info" - }) - } + (*main.SortableMessage)(0xc0003474c0)({ + fullName: (string) (len=5) "Empty", + name: (string) (len=5) "Empty", + fields: ([]*main.Field) }), - (string) (len=20) "ServiceStateResponse": (*main.Message)(0xc0003c9620)({ + (*main.SortableMessage)(0xc000347640)({ fullName: (string) (len=20) "ServiceStateResponse", name: (string) (len=20) "ServiceStateResponse", - fields: (map[string]*main.Field) (len=8) { - (string) (len=10) "admin_npub": (*main.Field)(0xc0003ee700)({ + fields: ([]*main.Field) (len=14 cap=16) { + (*main.Field)(0xc000346d00)({ name: (string) (len=10) "admin_npub", kind: (string) (len=6) "string", isMap: (bool) false, @@ -252,8 +231,18 @@ isOptional: (bool) false, oneOfName: (string) "" }), - (string) (len=15) "relay_connected": (*main.Field)(0xc0003ee740)({ - name: (string) (len=15) "relay_connected", + (*main.Field)(0xc000346fc0)({ + name: (string) (len=6) "app_id", + kind: (string) (len=6) "string", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346f00)({ + name: (string) (len=18) "automate_liquidity", kind: (string) (len=4) "bool", isMap: (bool) false, isArray: (bool) false, @@ -262,19 +251,9 @@ isOptional: (bool) false, oneOfName: (string) "" }), - (string) (len=9) "lnd_state": (*main.Field)(0xc0003ee780)({ - name: (string) (len=9) "lnd_state", - kind: (string) (len=8) "LndState", - isMap: (bool) false, - isArray: (bool) false, - isEnum: (bool) true, - isMessage: (bool) false, - isOptional: (bool) false, - oneOfName: (string) "" - }), - (string) (len=11) "watchdog_ok": (*main.Field)(0xc0003ee7c0)({ - name: (string) (len=11) "watchdog_ok", - kind: (string) (len=4) "bool", + (*main.Field)(0xc000346f80)({ + name: (string) (len=10) "avatar_url", + kind: (string) (len=6) "string", isMap: (bool) false, isArray: (bool) false, isEnum: (bool) false, @@ -282,7 +261,7 @@ isOptional: (bool) false, oneOfName: (string) "" }), - (string) (len=8) "http_url": (*main.Field)(0xc0003ee800)({ + (*main.Field)(0xc000346e00)({ name: (string) (len=8) "http_url", kind: (string) (len=6) "string", isMap: (bool) false, @@ -292,7 +271,17 @@ isOptional: (bool) false, oneOfName: (string) "" }), - (string) (len=8) "nprofile": (*main.Field)(0xc0003ee840)({ + (*main.Field)(0xc000346d80)({ + name: (string) (len=9) "lnd_state", + kind: (string) (len=8) "LndState", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) true, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346e40)({ name: (string) (len=8) "nprofile", kind: (string) (len=6) "string", isMap: (bool) false, @@ -302,7 +291,7 @@ isOptional: (bool) false, oneOfName: (string) "" }), - (string) (len=13) "provider_name": (*main.Field)(0xc0003ee680)({ + (*main.Field)(0xc000346c80)({ name: (string) (len=13) "provider_name", kind: (string) (len=6) "string", isMap: (bool) false, @@ -312,7 +301,37 @@ isOptional: (bool) false, oneOfName: (string) "" }), - (string) (len=6) "relays": (*main.Field)(0xc0003ee6c0)({ + (*main.Field)(0xc000346f40)({ + name: (string) (len=21) "push_backups_to_nostr", + kind: (string) (len=4) "bool", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346d40)({ + name: (string) (len=15) "relay_connected", + kind: (string) (len=4) "bool", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346ec0)({ + name: (string) (len=9) "relay_url", + kind: (string) (len=6) "string", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346cc0)({ name: (string) (len=6) "relays", kind: (string) (len=6) "string", isMap: (bool) false, @@ -321,39 +340,88 @@ isMessage: (bool) false, isOptional: (bool) false, oneOfName: (string) "" + }), + (*main.Field)(0xc000346e80)({ + name: (string) (len=11) "source_name", + kind: (string) (len=6) "string", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346dc0)({ + name: (string) (len=11) "watchdog_ok", + kind: (string) (len=4) "bool", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }) + } + }), + (*main.SortableMessage)(0xc000347500)({ + fullName: (string) (len=13) "StateResponse", + name: (string) (len=13) "StateResponse", + fields: ([]*main.Field) (len=2 cap=2) { + (*main.Field)(0xc000346a40)({ + name: (string) (len=12) "admin_linked", + kind: (string) (len=4) "bool", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" + }), + (*main.Field)(0xc000346a00)({ + name: (string) (len=11) "config_sent", + kind: (string) (len=4) "bool", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) "" }) } }) } -(map[string][]*main.Field) (len=1) { - (string) (len=37) "AdminConnectInfoResponse_connect_info": ([]*main.Field) (len=2 cap=2) { - (*main.Field)(0xc0003ee600)({ - name: (string) (len=11) "admin_token", - kind: (string) (len=6) "string", - isMap: (bool) false, - isArray: (bool) false, - isEnum: (bool) false, - isMessage: (bool) false, - isOptional: (bool) false, - oneOfName: (string) (len=12) "connect_info" - }), - (*main.Field)(0xc0003ee640)({ - name: (string) (len=13) "enrolled_npub", - kind: (string) (len=6) "string", - isMap: (bool) false, - isArray: (bool) false, - isEnum: (bool) false, - isMessage: (bool) false, - isOptional: (bool) false, - oneOfName: (string) (len=12) "connect_info" - }) - } +([]*main.OneOf) (len=1 cap=1) { + (*main.OneOf)(0xc0000e31a0)({ + kind: (string) (len=37) "AdminConnectInfoResponse_connect_info", + fields: ([]*main.Field) (len=2 cap=2) { + (*main.Field)(0xc000346c00)({ + name: (string) (len=11) "admin_token", + kind: (string) (len=6) "string", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) (len=12) "connect_info" + }), + (*main.Field)(0xc000346c40)({ + name: (string) (len=13) "enrolled_npub", + kind: (string) (len=6) "string", + isMap: (bool) false, + isArray: (bool) false, + isEnum: (bool) false, + isMessage: (bool) false, + isOptional: (bool) false, + oneOfName: (string) (len=12) "connect_info" + }) + } + }) } parsing file: wizard_structs 5 parsing file: wizard_methods 2 --> [{guest Guest map[]}] +-> [{guest Guest []}] ([]interface {}) diff --git a/proto/wizard_service/autogenerated/go/http_client.go b/proto/wizard_service/autogenerated/go/http_client.go new file mode 100644 index 00000000..e087af9a --- /dev/null +++ b/proto/wizard_service/autogenerated/go/http_client.go @@ -0,0 +1,151 @@ +// This file was autogenerated from a .proto file, DO NOT EDIT! + +package lightning_pub + +import ( + "net/url" + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +func doPostRequest(url string, body []byte, auth string) ([]byte, error) { + bodyReader := bytes.NewReader(body) + req, err := http.NewRequest(http.MethodPost, url, bodyReader) + if err != nil { + return nil, err + } + req.Header.Set("authorization", auth) + return doRequest(req) +} + +func doGetRequest(url string, auth string) ([]byte, error) { + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + req.Header.Set("authorization", auth) + return doRequest(req) +} + +func doRequest(req *http.Request) ([]byte, error) { + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + resBody, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + return resBody, nil +} + +type ClientParams struct { + BaseURL string + RetrieveGuestAuth func() (string, error) +} +type Client struct { + GetAdminConnectInfo func() (*AdminConnectInfoResponse, error) + GetServiceState func() (*ServiceStateResponse, error) + WizardConfig func(req ConfigRequest) error + WizardState func() (*StateResponse, error) +} + +func NewClient(params ClientParams) *Client { + return &Client{ + GetAdminConnectInfo: func() (*AdminConnectInfoResponse, error) { + auth, err := params.RetrieveGuestAuth() + if err != nil { + return nil, err + } + finalRoute := "/wizard/admin_connect_info" + resBody, err := doGetRequest(params.BaseURL+finalRoute, auth) + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := AdminConnectInfoResponse{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, + GetServiceState: func() (*ServiceStateResponse, error) { + auth, err := params.RetrieveGuestAuth() + if err != nil { + return nil, err + } + finalRoute := "/wizard/service_state" + resBody, err := doGetRequest(params.BaseURL+finalRoute, auth) + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := ServiceStateResponse{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, + WizardConfig: func(req ConfigRequest) error { + auth, err := params.RetrieveGuestAuth() + if err != nil { + return err + } + finalRoute := "/wizard/config" + body, err := json.Marshal(req) + if err != nil { + return err + } + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return err + } + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return err + } + if result.Status == "ERROR" { + return fmt.Errorf(result.Reason) + } + return nil + }, + WizardState: func() (*StateResponse, error) { + auth, err := params.RetrieveGuestAuth() + if err != nil { + return nil, err + } + finalRoute := "/wizard/state" + resBody, err := doGetRequest(params.BaseURL+finalRoute, auth) + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := StateResponse{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, + } +} diff --git a/proto/wizard_service/autogenerated/go/types.go b/proto/wizard_service/autogenerated/go/types.go new file mode 100644 index 00000000..c35bfc9c --- /dev/null +++ b/proto/wizard_service/autogenerated/go/types.go @@ -0,0 +1,63 @@ +// This file was autogenerated from a .proto file, DO NOT EDIT! + +package lightning_pub + +type ResultError struct { + Status string `json:"status"` + Reason string `json:"reason"` +} +type GuestContext struct { +} +type LndState string + +const ( + OFFLINE LndState = "OFFLINE" + ONLINE LndState = "ONLINE" + SYNCING LndState = "SYNCING" +) + +type AdminConnectInfoResponse struct { + Connect_info *AdminConnectInfoResponse_connect_info `json:"connect_info"` + Nprofile string `json:"nprofile"` +} +type ConfigRequest struct { + Automate_liquidity bool `json:"automate_liquidity"` + Avatar_url string `json:"avatar_url"` + Push_backups_to_nostr bool `json:"push_backups_to_nostr"` + Relay_url string `json:"relay_url"` + Source_name string `json:"source_name"` +} +type Empty struct { +} +type ServiceStateResponse struct { + Admin_npub string `json:"admin_npub"` + App_id string `json:"app_id"` + Automate_liquidity bool `json:"automate_liquidity"` + Avatar_url string `json:"avatar_url"` + Http_url string `json:"http_url"` + Lnd_state LndState `json:"lnd_state"` + Nprofile string `json:"nprofile"` + Provider_name string `json:"provider_name"` + Push_backups_to_nostr bool `json:"push_backups_to_nostr"` + Relay_connected bool `json:"relay_connected"` + Relay_url string `json:"relay_url"` + Relays []string `json:"relays"` + Source_name string `json:"source_name"` + Watchdog_ok bool `json:"watchdog_ok"` +} +type StateResponse struct { + Admin_linked bool `json:"admin_linked"` + Config_sent bool `json:"config_sent"` +} +type AdminConnectInfoResponse_connect_info_type string + +const ( + ADMIN_TOKEN AdminConnectInfoResponse_connect_info_type = "admin_token" + ENROLLED_NPUB AdminConnectInfoResponse_connect_info_type = "enrolled_npub" +) + +type AdminConnectInfoResponse_connect_info struct { + Type AdminConnectInfoResponse_connect_info_type `json:"type"` + Admin_token *string `json:"admin_token"` + Enrolled_npub *string `json:"enrolled_npub"` +} diff --git a/proto/wizard_service/autogenerated/ts/express_server.ts b/proto/wizard_service/autogenerated/ts/express_server.ts index 7dd26057..3fe9fdbd 100644 --- a/proto/wizard_service/autogenerated/ts/express_server.ts +++ b/proto/wizard_service/autogenerated/ts/express_server.ts @@ -29,47 +29,6 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { app.use(json()) app.use(urlencoded({ extended: true })) if (opts.logMethod) app.use((req, _, next) => { console.log(req.method, req.path); if (opts.logBody) console.log(req.body); next() }) - if (!opts.allowNotImplementedMethods && !methods.WizardState) throw new Error('method: WizardState is not implemented') - app.get('/wizard/state', async (req, res) => { - const info: Types.RequestInfo = { rpcName: 'WizardState', batch: false, nostr: false, batchSize: 0} - const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n } - let authCtx: Types.AuthContext = {} - try { - if (!methods.WizardState) throw new Error('method: WizardState is not implemented') - const authContext = await opts.GuestAuthGuard(req.headers['authorization']) - authCtx = authContext - stats.guard = process.hrtime.bigint() - stats.validate = stats.guard - const query = req.query - const params = req.params - const response = await methods.WizardState({rpcName:'WizardState', ctx:authContext }) - stats.handle = process.hrtime.bigint() - res.json({status: 'OK', ...response}) - opts.metricsCallback([{ ...info, ...stats, ...authContext }]) - } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } - }) - if (!opts.allowNotImplementedMethods && !methods.WizardConfig) throw new Error('method: WizardConfig is not implemented') - app.post('/wizard/config', async (req, res) => { - const info: Types.RequestInfo = { rpcName: 'WizardConfig', batch: false, nostr: false, batchSize: 0} - const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n } - let authCtx: Types.AuthContext = {} - try { - if (!methods.WizardConfig) throw new Error('method: WizardConfig is not implemented') - const authContext = await opts.GuestAuthGuard(req.headers['authorization']) - authCtx = authContext - stats.guard = process.hrtime.bigint() - const request = req.body - const error = Types.ConfigRequestValidate(request) - stats.validate = process.hrtime.bigint() - if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) - const query = req.query - const params = req.params - await methods.WizardConfig({rpcName:'WizardConfig', ctx:authContext , req: request}) - stats.handle = process.hrtime.bigint() - res.json({status: 'OK'}) - opts.metricsCallback([{ ...info, ...stats, ...authContext }]) - } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } - }) if (!opts.allowNotImplementedMethods && !methods.GetAdminConnectInfo) throw new Error('method: GetAdminConnectInfo is not implemented') app.get('/wizard/admin_connect_info', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'GetAdminConnectInfo', batch: false, nostr: false, batchSize: 0} @@ -108,6 +67,47 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { opts.metricsCallback([{ ...info, ...stats, ...authContext }]) } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } }) + if (!opts.allowNotImplementedMethods && !methods.WizardConfig) throw new Error('method: WizardConfig is not implemented') + app.post('/wizard/config', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'WizardConfig', batch: false, nostr: false, batchSize: 0} + const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n } + let authCtx: Types.AuthContext = {} + try { + if (!methods.WizardConfig) throw new Error('method: WizardConfig is not implemented') + const authContext = await opts.GuestAuthGuard(req.headers['authorization']) + authCtx = authContext + stats.guard = process.hrtime.bigint() + const request = req.body + const error = Types.ConfigRequestValidate(request) + stats.validate = process.hrtime.bigint() + if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) + const query = req.query + const params = req.params + await methods.WizardConfig({rpcName:'WizardConfig', ctx:authContext , req: request}) + stats.handle = process.hrtime.bigint() + res.json({status: 'OK'}) + opts.metricsCallback([{ ...info, ...stats, ...authContext }]) + } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } + }) + if (!opts.allowNotImplementedMethods && !methods.WizardState) throw new Error('method: WizardState is not implemented') + app.get('/wizard/state', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'WizardState', batch: false, nostr: false, batchSize: 0} + const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n } + let authCtx: Types.AuthContext = {} + try { + if (!methods.WizardState) throw new Error('method: WizardState is not implemented') + const authContext = await opts.GuestAuthGuard(req.headers['authorization']) + authCtx = authContext + stats.guard = process.hrtime.bigint() + stats.validate = stats.guard + const query = req.query + const params = req.params + const response = await methods.WizardState({rpcName:'WizardState', ctx:authContext }) + stats.handle = process.hrtime.bigint() + res.json({status: 'OK', ...response}) + opts.metricsCallback([{ ...info, ...stats, ...authContext }]) + } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } + }) if (opts.staticFiles) { app.use(express.static(opts.staticFiles)) app.get('*', function (_, res) { res.sendFile('index.html', { root: opts.staticFiles })}) diff --git a/proto/wizard_service/autogenerated/ts/http_client.ts b/proto/wizard_service/autogenerated/ts/http_client.ts index 8d949b84..d3a9ba95 100644 --- a/proto/wizard_service/autogenerated/ts/http_client.ts +++ b/proto/wizard_service/autogenerated/ts/http_client.ts @@ -12,31 +12,6 @@ export type ClientParams = { checkResult?: true } export default (params: ClientParams) => ({ - WizardState: async (): Promise => { - const auth = await params.retrieveGuestAuth() - if (auth === null) throw new Error('retrieveGuestAuth() returned null') - let finalRoute = '/wizard/state' - const { data } = await axios.get(params.baseUrl + finalRoute, { headers: { 'authorization': auth } }) - if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { - const result = data - if(!params.checkResult) return { status: 'OK', ...result } - const error = Types.StateResponseValidate(result) - if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } - } - return { status: 'ERROR', reason: 'invalid response' } - }, - WizardConfig: async (request: Types.ConfigRequest): Promise => { - const auth = await params.retrieveGuestAuth() - if (auth === null) throw new Error('retrieveGuestAuth() returned null') - let finalRoute = '/wizard/config' - const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } }) - if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { - return data - } - return { status: 'ERROR', reason: 'invalid response' } - }, GetAdminConnectInfo: async (): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') @@ -65,4 +40,29 @@ export default (params: ClientParams) => ({ } return { status: 'ERROR', reason: 'invalid response' } }, + WizardConfig: async (request: Types.ConfigRequest): Promise => { + const auth = await params.retrieveGuestAuth() + if (auth === null) throw new Error('retrieveGuestAuth() returned null') + let finalRoute = '/wizard/config' + const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } }) + if (data.status === 'ERROR' && typeof data.reason === 'string') return data + if (data.status === 'OK') { + return data + } + return { status: 'ERROR', reason: 'invalid response' } + }, + WizardState: async (): Promise => { + const auth = await params.retrieveGuestAuth() + if (auth === null) throw new Error('retrieveGuestAuth() returned null') + let finalRoute = '/wizard/state' + const { data } = await axios.get(params.baseUrl + finalRoute, { headers: { 'authorization': auth } }) + if (data.status === 'ERROR' && typeof data.reason === 'string') return data + if (data.status === 'OK') { + const result = data + if(!params.checkResult) return { status: 'OK', ...result } + const error = Types.StateResponseValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, }) diff --git a/proto/wizard_service/autogenerated/ts/types.ts b/proto/wizard_service/autogenerated/ts/types.ts index 43927811..80026099 100644 --- a/proto/wizard_service/autogenerated/ts/types.ts +++ b/proto/wizard_service/autogenerated/ts/types.ts @@ -6,33 +6,33 @@ export type RequestStats = { startMs:number, start:bigint, parse: bigint, guard: export type RequestMetric = AuthContext & RequestInfo & RequestStats & { error?: string } export type GuestContext = { } -export type GuestMethodInputs = WizardState_Input | WizardConfig_Input | GetAdminConnectInfo_Input | GetServiceState_Input -export type GuestMethodOutputs = WizardState_Output | WizardConfig_Output | GetAdminConnectInfo_Output | GetServiceState_Output +export type GuestMethodInputs = GetAdminConnectInfo_Input | GetServiceState_Input | WizardConfig_Input | WizardState_Input +export type GuestMethodOutputs = GetAdminConnectInfo_Output | GetServiceState_Output | WizardConfig_Output | WizardState_Output export type AuthContext = GuestContext -export type WizardState_Input = {rpcName:'WizardState'} -export type WizardState_Output = ResultError | ({ status: 'OK' } & StateResponse) - -export type WizardConfig_Input = {rpcName:'WizardConfig', req: ConfigRequest} -export type WizardConfig_Output = ResultError | { status: 'OK' } - export type GetAdminConnectInfo_Input = {rpcName:'GetAdminConnectInfo'} export type GetAdminConnectInfo_Output = ResultError | ({ status: 'OK' } & AdminConnectInfoResponse) export type GetServiceState_Input = {rpcName:'GetServiceState'} export type GetServiceState_Output = ResultError | ({ status: 'OK' } & ServiceStateResponse) +export type WizardConfig_Input = {rpcName:'WizardConfig', req: ConfigRequest} +export type WizardConfig_Output = ResultError | { status: 'OK' } + +export type WizardState_Input = {rpcName:'WizardState'} +export type WizardState_Output = ResultError | ({ status: 'OK' } & StateResponse) + export type ServerMethods = { - WizardState?: (req: WizardState_Input & {ctx: GuestContext }) => Promise - WizardConfig?: (req: WizardConfig_Input & {ctx: GuestContext }) => Promise GetAdminConnectInfo?: (req: GetAdminConnectInfo_Input & {ctx: GuestContext }) => Promise GetServiceState?: (req: GetServiceState_Input & {ctx: GuestContext }) => Promise + WizardConfig?: (req: WizardConfig_Input & {ctx: GuestContext }) => Promise + WizardState?: (req: WizardState_Input & {ctx: GuestContext }) => Promise } export enum LndState { OFFLINE = 'OFFLINE', - SYNCING = 'SYNCING', ONLINE = 'ONLINE', + SYNCING = 'SYNCING', } export const enumCheckLndState = (e?: LndState): boolean => { for (const v in LndState) if (e === v) return true @@ -43,6 +43,68 @@ export type OptionsBaseMessage = { allOptionalsAreSet?: true } +export type AdminConnectInfoResponse = { + connect_info: AdminConnectInfoResponse_connect_info + nprofile: string +} +export const AdminConnectInfoResponseOptionalFields: [] = [] +export type AdminConnectInfoResponseOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + connect_info_Options?: AdminConnectInfoResponse_connect_infoOptions + nprofile_CustomCheck?: (v: string) => boolean +} +export const AdminConnectInfoResponseValidate = (o?: AdminConnectInfoResponse, opts: AdminConnectInfoResponseOptions = {}, path: string = 'AdminConnectInfoResponse::root.'): Error | null => { + if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + + const connect_infoErr = AdminConnectInfoResponse_connect_infoValidate(o.connect_info, opts.connect_info_Options, `${path}.connect_info`) + if (connect_infoErr !== null) return connect_infoErr + + + if (typeof o.nprofile !== 'string') return new Error(`${path}.nprofile: is not a string`) + if (opts.nprofile_CustomCheck && !opts.nprofile_CustomCheck(o.nprofile)) return new Error(`${path}.nprofile: custom check failed`) + + return null +} + +export type ConfigRequest = { + automate_liquidity: boolean + avatar_url: string + push_backups_to_nostr: boolean + relay_url: string + source_name: string +} +export const ConfigRequestOptionalFields: [] = [] +export type ConfigRequestOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + automate_liquidity_CustomCheck?: (v: boolean) => boolean + avatar_url_CustomCheck?: (v: string) => boolean + push_backups_to_nostr_CustomCheck?: (v: boolean) => boolean + relay_url_CustomCheck?: (v: string) => boolean + source_name_CustomCheck?: (v: string) => boolean +} +export const ConfigRequestValidate = (o?: ConfigRequest, opts: ConfigRequestOptions = {}, path: string = 'ConfigRequest::root.'): Error | null => { + if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + + if (typeof o.automate_liquidity !== 'boolean') return new Error(`${path}.automate_liquidity: is not a boolean`) + if (opts.automate_liquidity_CustomCheck && !opts.automate_liquidity_CustomCheck(o.automate_liquidity)) return new Error(`${path}.automate_liquidity: custom check failed`) + + if (typeof o.avatar_url !== 'string') return new Error(`${path}.avatar_url: is not a string`) + if (opts.avatar_url_CustomCheck && !opts.avatar_url_CustomCheck(o.avatar_url)) return new Error(`${path}.avatar_url: custom check failed`) + + if (typeof o.push_backups_to_nostr !== 'boolean') return new Error(`${path}.push_backups_to_nostr: is not a boolean`) + if (opts.push_backups_to_nostr_CustomCheck && !opts.push_backups_to_nostr_CustomCheck(o.push_backups_to_nostr)) return new Error(`${path}.push_backups_to_nostr: custom check failed`) + + if (typeof o.relay_url !== 'string') return new Error(`${path}.relay_url: is not a string`) + if (opts.relay_url_CustomCheck && !opts.relay_url_CustomCheck(o.relay_url)) return new Error(`${path}.relay_url: custom check failed`) + + if (typeof o.source_name !== 'string') return new Error(`${path}.source_name: is not a string`) + if (opts.source_name_CustomCheck && !opts.source_name_CustomCheck(o.source_name)) return new Error(`${path}.source_name: custom check failed`) + + return null +} + export type Empty = { } export const EmptyOptionalFields: [] = [] @@ -56,138 +118,111 @@ export const EmptyValidate = (o?: Empty, opts: EmptyOptions = {}, path: string = return null } -export type StateResponse = { - config_sent: boolean - admin_linked: boolean -} -export const StateResponseOptionalFields: [] = [] -export type StateResponseOptions = OptionsBaseMessage & { - checkOptionalsAreSet?: [] - config_sent_CustomCheck?: (v: boolean) => boolean - admin_linked_CustomCheck?: (v: boolean) => boolean -} -export const StateResponseValidate = (o?: StateResponse, opts: StateResponseOptions = {}, path: string = 'StateResponse::root.'): Error | null => { - if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') - if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') - - if (typeof o.config_sent !== 'boolean') return new Error(`${path}.config_sent: is not a boolean`) - if (opts.config_sent_CustomCheck && !opts.config_sent_CustomCheck(o.config_sent)) return new Error(`${path}.config_sent: custom check failed`) - - if (typeof o.admin_linked !== 'boolean') return new Error(`${path}.admin_linked: is not a boolean`) - if (opts.admin_linked_CustomCheck && !opts.admin_linked_CustomCheck(o.admin_linked)) return new Error(`${path}.admin_linked: custom check failed`) - - return null -} - -export type ConfigRequest = { - source_name: string - relay_url: string - automate_liquidity: boolean - push_backups_to_nostr: boolean -} -export const ConfigRequestOptionalFields: [] = [] -export type ConfigRequestOptions = OptionsBaseMessage & { - checkOptionalsAreSet?: [] - push_backups_to_nostr_CustomCheck?: (v: boolean) => boolean - source_name_CustomCheck?: (v: string) => boolean - relay_url_CustomCheck?: (v: string) => boolean - automate_liquidity_CustomCheck?: (v: boolean) => boolean -} -export const ConfigRequestValidate = (o?: ConfigRequest, opts: ConfigRequestOptions = {}, path: string = 'ConfigRequest::root.'): Error | null => { - if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') - if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') - - if (typeof o.source_name !== 'string') return new Error(`${path}.source_name: is not a string`) - if (opts.source_name_CustomCheck && !opts.source_name_CustomCheck(o.source_name)) return new Error(`${path}.source_name: custom check failed`) - - if (typeof o.relay_url !== 'string') return new Error(`${path}.relay_url: is not a string`) - if (opts.relay_url_CustomCheck && !opts.relay_url_CustomCheck(o.relay_url)) return new Error(`${path}.relay_url: custom check failed`) - - if (typeof o.automate_liquidity !== 'boolean') return new Error(`${path}.automate_liquidity: is not a boolean`) - if (opts.automate_liquidity_CustomCheck && !opts.automate_liquidity_CustomCheck(o.automate_liquidity)) return new Error(`${path}.automate_liquidity: custom check failed`) - - if (typeof o.push_backups_to_nostr !== 'boolean') return new Error(`${path}.push_backups_to_nostr: is not a boolean`) - if (opts.push_backups_to_nostr_CustomCheck && !opts.push_backups_to_nostr_CustomCheck(o.push_backups_to_nostr)) return new Error(`${path}.push_backups_to_nostr: custom check failed`) - - return null -} - -export type AdminConnectInfoResponse = { - nprofile: string - connect_info: AdminConnectInfoResponse_connect_info -} -export const AdminConnectInfoResponseOptionalFields: [] = [] -export type AdminConnectInfoResponseOptions = OptionsBaseMessage & { - checkOptionalsAreSet?: [] - nprofile_CustomCheck?: (v: string) => boolean - connect_info_CustomCheck?: (v: AdminConnectInfoResponse_connect_info) => boolean -} -export const AdminConnectInfoResponseValidate = (o?: AdminConnectInfoResponse, opts: AdminConnectInfoResponseOptions = {}, path: string = 'AdminConnectInfoResponse::root.'): Error | null => { - if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') - if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') - - if (typeof o.nprofile !== 'string') return new Error(`${path}.nprofile: is not a string`) - if (opts.nprofile_CustomCheck && !opts.nprofile_CustomCheck(o.nprofile)) return new Error(`${path}.nprofile: custom check failed`) - - const connect_infoErr = AdminConnectInfoResponse_connect_infoValidate(o.connect_info,{}, `${path}.connect_info`) - if (connect_infoErr !== null) return connect_infoErr - - - return null -} - export type ServiceStateResponse = { + admin_npub: string + app_id: string + automate_liquidity: boolean + avatar_url: string http_url: string + lnd_state: LndState nprofile: string provider_name: string - relays: string[] - admin_npub: string + push_backups_to_nostr: boolean relay_connected: boolean - lnd_state: LndState + relay_url: string + relays: string[] + source_name: string watchdog_ok: boolean } export const ServiceStateResponseOptionalFields: [] = [] export type ServiceStateResponseOptions = OptionsBaseMessage & { checkOptionalsAreSet?: [] + admin_npub_CustomCheck?: (v: string) => boolean + app_id_CustomCheck?: (v: string) => boolean + automate_liquidity_CustomCheck?: (v: boolean) => boolean + avatar_url_CustomCheck?: (v: string) => boolean http_url_CustomCheck?: (v: string) => boolean + lnd_state_CustomCheck?: (v: LndState) => boolean nprofile_CustomCheck?: (v: string) => boolean provider_name_CustomCheck?: (v: string) => boolean - relays_CustomCheck?: (v: string[]) => boolean - admin_npub_CustomCheck?: (v: string) => boolean + push_backups_to_nostr_CustomCheck?: (v: boolean) => boolean relay_connected_CustomCheck?: (v: boolean) => boolean - lnd_state_CustomCheck?: (v: LndState) => boolean + relay_url_CustomCheck?: (v: string) => boolean + relays_CustomCheck?: (v: string[]) => boolean + source_name_CustomCheck?: (v: string) => boolean watchdog_ok_CustomCheck?: (v: boolean) => boolean } export const ServiceStateResponseValidate = (o?: ServiceStateResponse, opts: ServiceStateResponseOptions = {}, path: string = 'ServiceStateResponse::root.'): Error | null => { if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + if (typeof o.admin_npub !== 'string') return new Error(`${path}.admin_npub: is not a string`) + if (opts.admin_npub_CustomCheck && !opts.admin_npub_CustomCheck(o.admin_npub)) return new Error(`${path}.admin_npub: custom check failed`) + + if (typeof o.app_id !== 'string') return new Error(`${path}.app_id: is not a string`) + if (opts.app_id_CustomCheck && !opts.app_id_CustomCheck(o.app_id)) return new Error(`${path}.app_id: custom check failed`) + + if (typeof o.automate_liquidity !== 'boolean') return new Error(`${path}.automate_liquidity: is not a boolean`) + if (opts.automate_liquidity_CustomCheck && !opts.automate_liquidity_CustomCheck(o.automate_liquidity)) return new Error(`${path}.automate_liquidity: custom check failed`) + + if (typeof o.avatar_url !== 'string') return new Error(`${path}.avatar_url: is not a string`) + if (opts.avatar_url_CustomCheck && !opts.avatar_url_CustomCheck(o.avatar_url)) return new Error(`${path}.avatar_url: custom check failed`) + + if (typeof o.http_url !== 'string') return new Error(`${path}.http_url: is not a string`) + if (opts.http_url_CustomCheck && !opts.http_url_CustomCheck(o.http_url)) return new Error(`${path}.http_url: custom check failed`) + + if (!enumCheckLndState(o.lnd_state)) return new Error(`${path}.lnd_state: is not a valid LndState`) + if (opts.lnd_state_CustomCheck && !opts.lnd_state_CustomCheck(o.lnd_state)) return new Error(`${path}.lnd_state: custom check failed`) + + if (typeof o.nprofile !== 'string') return new Error(`${path}.nprofile: is not a string`) + if (opts.nprofile_CustomCheck && !opts.nprofile_CustomCheck(o.nprofile)) return new Error(`${path}.nprofile: custom check failed`) + + if (typeof o.provider_name !== 'string') return new Error(`${path}.provider_name: is not a string`) + if (opts.provider_name_CustomCheck && !opts.provider_name_CustomCheck(o.provider_name)) return new Error(`${path}.provider_name: custom check failed`) + + if (typeof o.push_backups_to_nostr !== 'boolean') return new Error(`${path}.push_backups_to_nostr: is not a boolean`) + if (opts.push_backups_to_nostr_CustomCheck && !opts.push_backups_to_nostr_CustomCheck(o.push_backups_to_nostr)) return new Error(`${path}.push_backups_to_nostr: custom check failed`) + + if (typeof o.relay_connected !== 'boolean') return new Error(`${path}.relay_connected: is not a boolean`) + if (opts.relay_connected_CustomCheck && !opts.relay_connected_CustomCheck(o.relay_connected)) return new Error(`${path}.relay_connected: custom check failed`) + + if (typeof o.relay_url !== 'string') return new Error(`${path}.relay_url: is not a string`) + if (opts.relay_url_CustomCheck && !opts.relay_url_CustomCheck(o.relay_url)) return new Error(`${path}.relay_url: custom check failed`) + if (!Array.isArray(o.relays)) return new Error(`${path}.relays: is not an array`) for (let index = 0; index < o.relays.length; index++) { if (typeof o.relays[index] !== 'string') return new Error(`${path}.relays[${index}]: is not a string`) } if (opts.relays_CustomCheck && !opts.relays_CustomCheck(o.relays)) return new Error(`${path}.relays: custom check failed`) - if (typeof o.admin_npub !== 'string') return new Error(`${path}.admin_npub: is not a string`) - if (opts.admin_npub_CustomCheck && !opts.admin_npub_CustomCheck(o.admin_npub)) return new Error(`${path}.admin_npub: custom check failed`) - - if (typeof o.relay_connected !== 'boolean') return new Error(`${path}.relay_connected: is not a boolean`) - if (opts.relay_connected_CustomCheck && !opts.relay_connected_CustomCheck(o.relay_connected)) return new Error(`${path}.relay_connected: custom check failed`) - - if (!enumCheckLndState(o.lnd_state)) return new Error(`${path}.lnd_state: is not a valid LndState`) - if (opts.lnd_state_CustomCheck && !opts.lnd_state_CustomCheck(o.lnd_state)) return new Error(`${path}.lnd_state: custom check failed`) + if (typeof o.source_name !== 'string') return new Error(`${path}.source_name: is not a string`) + if (opts.source_name_CustomCheck && !opts.source_name_CustomCheck(o.source_name)) return new Error(`${path}.source_name: custom check failed`) if (typeof o.watchdog_ok !== 'boolean') return new Error(`${path}.watchdog_ok: is not a boolean`) if (opts.watchdog_ok_CustomCheck && !opts.watchdog_ok_CustomCheck(o.watchdog_ok)) return new Error(`${path}.watchdog_ok: custom check failed`) - if (typeof o.http_url !== 'string') return new Error(`${path}.http_url: is not a string`) - if (opts.http_url_CustomCheck && !opts.http_url_CustomCheck(o.http_url)) return new Error(`${path}.http_url: custom check failed`) + return null +} - if (typeof o.nprofile !== 'string') return new Error(`${path}.nprofile: is not a string`) - if (opts.nprofile_CustomCheck && !opts.nprofile_CustomCheck(o.nprofile)) return new Error(`${path}.nprofile: custom check failed`) +export type StateResponse = { + admin_linked: boolean + config_sent: boolean +} +export const StateResponseOptionalFields: [] = [] +export type StateResponseOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + admin_linked_CustomCheck?: (v: boolean) => boolean + config_sent_CustomCheck?: (v: boolean) => boolean +} +export const StateResponseValidate = (o?: StateResponse, opts: StateResponseOptions = {}, path: string = 'StateResponse::root.'): Error | null => { + if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') - if (typeof o.provider_name !== 'string') return new Error(`${path}.provider_name: is not a string`) - if (opts.provider_name_CustomCheck && !opts.provider_name_CustomCheck(o.provider_name)) return new Error(`${path}.provider_name: custom check failed`) + if (typeof o.admin_linked !== 'boolean') return new Error(`${path}.admin_linked: is not a boolean`) + if (opts.admin_linked_CustomCheck && !opts.admin_linked_CustomCheck(o.admin_linked)) return new Error(`${path}.admin_linked: custom check failed`) + + if (typeof o.config_sent !== 'boolean') return new Error(`${path}.config_sent: is not a boolean`) + if (opts.config_sent_CustomCheck && !opts.config_sent_CustomCheck(o.config_sent)) return new Error(`${path}.config_sent: custom check failed`) return null } @@ -196,19 +231,34 @@ export enum AdminConnectInfoResponse_connect_info_type { ADMIN_TOKEN = 'admin_token', ENROLLED_NPUB = 'enrolled_npub', } +export const enumCheckAdminConnectInfoResponse_connect_info_type = (e?: AdminConnectInfoResponse_connect_info_type): boolean => { + for (const v in AdminConnectInfoResponse_connect_info_type) if (e === v) return true + return false +} export type AdminConnectInfoResponse_connect_info = {type:AdminConnectInfoResponse_connect_info_type.ADMIN_TOKEN, admin_token:string}| {type:AdminConnectInfoResponse_connect_info_type.ENROLLED_NPUB, enrolled_npub:string} -export const AdminConnectInfoResponse_connect_infoValidate = (o?: AdminConnectInfoResponse_connect_info, opts = {}, path: string = 'AdminConnectInfoResponse_connect_info::root.'): Error | null => { - if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') - switch (o.type) { - case 'admin_token': - if (typeof o.admin_token !== 'string') return new Error(`${path}.admin_token: is not a string`) - break - case 'enrolled_npub': - if (typeof o.enrolled_npub !== 'string') return new Error(`${path}.enrolled_npub: is not a string`) - break - } - return new Error(path + ': unknown type'+ o.type) +export type AdminConnectInfoResponse_connect_infoOptions = { + admin_token_CustomCheck?: (v: string) => boolean + enrolled_npub_CustomCheck?: (v: string) => boolean +} +export const AdminConnectInfoResponse_connect_infoValidate = (o?: AdminConnectInfoResponse_connect_info, opts:AdminConnectInfoResponse_connect_infoOptions = {}, path: string = 'AdminConnectInfoResponse_connect_info::root.'): Error | null => { + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + const stringType: string = o.type + switch (o.type) { + case AdminConnectInfoResponse_connect_info_type.ADMIN_TOKEN: + if (typeof o.admin_token !== 'string') return new Error(`${path}.admin_token: is not a string`) + if (opts.admin_token_CustomCheck && !opts.admin_token_CustomCheck(o.admin_token)) return new Error(`${path}.admin_token: custom check failed`) + + break + case AdminConnectInfoResponse_connect_info_type.ENROLLED_NPUB: + if (typeof o.enrolled_npub !== 'string') return new Error(`${path}.enrolled_npub: is not a string`) + if (opts.enrolled_npub_CustomCheck && !opts.enrolled_npub_CustomCheck(o.enrolled_npub)) return new Error(`${path}.enrolled_npub: custom check failed`) + + break + default: + return new Error(path + ': unknown type '+ stringType) + } + return null } diff --git a/scripts/install.sh b/scripts/install.sh index d08e88f2..171d0b1c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -11,7 +11,7 @@ log() { echo -e "$(echo "$message" | sed 's/\\e\[[0-9;]*m//g')" >> "$TMP_LOG_FILE" } -SCRIPT_VERSION="0.2.1" +SCRIPT_VERSION="0.2.2" REPO="shocknet/Lightning.Pub" BRANCH="master" diff --git a/scripts/install_lnd.sh b/scripts/install_lnd.sh index 81061d07..af53650c 100755 --- a/scripts/install_lnd.sh +++ b/scripts/install_lnd.sh @@ -95,7 +95,11 @@ install_lnd() { # Check for and add default settings only if the keys are missing. grep -q "^bitcoin.mainnet=" $USER_HOME/.lnd/lnd.conf || echo "bitcoin.mainnet=true" >> $USER_HOME/.lnd/lnd.conf grep -q "^bitcoin.node=" $USER_HOME/.lnd/lnd.conf || echo "bitcoin.node=neutrino" >> $USER_HOME/.lnd/lnd.conf - grep -q "^neutrino.addpeer=" $USER_HOME/.lnd/lnd.conf || echo "neutrino.addpeer=neutrino.shock.network" >> $USER_HOME/.lnd/lnd.conf + grep -q "^neutrino.addpeer=neutrino.shock.network" $USER_HOME/.lnd/lnd.conf || echo "neutrino.addpeer=neutrino.shock.network" >> $USER_HOME/.lnd/lnd.conf + grep -q "^neutrino.addpeer=asia.blixtwallet.com" $USER_HOME/.lnd/lnd.conf || echo "neutrino.addpeer=asia.blixtwallet.com" >> $USER_HOME/.lnd/lnd.conf + grep -q "^neutrino.addpeer=europe.blixtwallet.com" $USER_HOME/.lnd/lnd.conf || echo "neutrino.addpeer=europe.blixtwallet.com" >> $USER_HOME/.lnd/lnd.conf + grep -q "^neutrino.addpeer=btcd.lnolymp.us" $USER_HOME/.lnd/lnd.conf || echo "neutrino.addpeer=btcd.lnolymp.us" >> $USER_HOME/.lnd/lnd.conf + grep -q "^neutrino.addpeer=btcd-mainnet.lightning.computer" $USER_HOME/.lnd/lnd.conf || echo "neutrino.addpeer=btcd-mainnet.lightning.computer" >> $USER_HOME/.lnd/lnd.conf grep -q "^fee.url=" $USER_HOME/.lnd/lnd.conf || echo "fee.url=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json" >> $USER_HOME/.lnd/lnd.conf chmod 600 $USER_HOME/.lnd/lnd.conf diff --git a/src/nostrMiddleware.ts b/src/nostrMiddleware.ts index fe81f84b..c17e2d01 100644 --- a/src/nostrMiddleware.ts +++ b/src/nostrMiddleware.ts @@ -80,7 +80,10 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett }, event.startAtNano, event.startAtMs) }) - return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args), Ping: () => nostr.Ping() } + // Mark nostr connected/ready after initial subscription tick + mainHandler.adminManager.setNostrConnected(true) + + return { Stop: () => { mainHandler.adminManager.setNostrConnected(false); return nostr.Stop }, Send: (...args) => nostr.Send(...args), Ping: () => nostr.Ping() } } diff --git a/src/services/main/adminManager.ts b/src/services/main/adminManager.ts index 6deec53a..c3927f87 100644 --- a/src/services/main/adminManager.ts +++ b/src/services/main/adminManager.ts @@ -22,6 +22,7 @@ export class AdminManager { interval: NodeJS.Timer appNprofile: string lnd: LND + nostrConnected: boolean = false constructor(mainSettings: MainSettings, storage: Storage) { this.storage = storage this.dataDir = mainSettings.storageSettings.dataDir @@ -42,6 +43,14 @@ export class AdminManager { this.lnd = lnd } + setNostrConnected = (connected: boolean) => { + this.nostrConnected = connected + } + + GetNostrConnected = () => { + return this.nostrConnected + } + setAppNprofile = (nprofile: string) => { this.appNprofile = nprofile const enrollToken = this.ReadAdminEnrollToken() diff --git a/src/services/main/index.ts b/src/services/main/index.ts index 3b98603c..71c64592 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -99,7 +99,7 @@ export default class { StartBeacons() { this.applicationManager.StartAppsServiceBeacon(app => { - this.UpdateBeacon(app, { type: 'service', name: app.name }) + this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: (app as any).avatar_url }) }) } @@ -386,7 +386,7 @@ export default class { }) } - async UpdateBeacon(app: Application, content: { type: 'service', name: string }) { + async UpdateBeacon(app: Application, content: { type: 'service', name: string, avatarUrl?: string }) { if (!app.nostr_public_key) { getLogger({ appName: app.name })("cannot update beacon, public key not set") return diff --git a/src/services/storage/entity/Application.ts b/src/services/storage/entity/Application.ts index e4367c07..637a2f41 100644 --- a/src/services/storage/entity/Application.ts +++ b/src/services/storage/entity/Application.ts @@ -27,6 +27,9 @@ export class Application { @Column({ nullable: true, unique: true }) nostr_public_key?: string + @Column({ nullable: true }) + avatar_url?: string + @CreateDateColumn() created_at: Date diff --git a/src/services/storage/migrations/1761000001000-application_avatar_url.ts b/src/services/storage/migrations/1761000001000-application_avatar_url.ts new file mode 100644 index 00000000..9c16a5ee --- /dev/null +++ b/src/services/storage/migrations/1761000001000-application_avatar_url.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ApplicationAvatarUrl1761000001000 implements MigrationInterface { + name = 'ApplicationAvatarUrl1761000001000' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "application" ADD COLUMN "avatar_url" varchar`); + } + + public async down(queryRunner: QueryRunner): Promise { + // SQLite has limited ALTER TABLE support; we can recreate table if needed, but for now just keep it simple + // No-op: dropping a column is non-trivial; leave column in place on downgrade + } +} + + diff --git a/src/services/storage/migrations/runner.ts b/src/services/storage/migrations/runner.ts index f22ce37e..37f89b0a 100644 --- a/src/services/storage/migrations/runner.ts +++ b/src/services/storage/migrations/runner.ts @@ -25,12 +25,13 @@ import { OldSomethingLeftover1753106599604 } from './1753106599604-old_something import { UserReceivingInvoiceIdx1753109184611 } from './1753109184611-user_receiving_invoice_idx.js' import { UserAccess1759426050669 } from './1759426050669-user_access.js' import { AddBlindToUserOffer1760000000000 } from './1760000000000-add_blind_to_user_offer.js' +import { ApplicationAvatarUrl1761000001000 } from './1761000001000-application_avatar_url.js' export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513, - InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000] + InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000] export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411] /* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise => { diff --git a/src/services/wizard/index.ts b/src/services/wizard/index.ts index 91c2ceea..f28095c3 100644 --- a/src/services/wizard/index.ts +++ b/src/services/wizard/index.ts @@ -40,19 +40,63 @@ export class Wizard { } GetServiceState = async (): Promise => { - const apps = await this.storage.applicationStorage.GetApplications() - const appNamesList = apps.map(app => app.name).join(', ') - return { - admin_npub: this.adminManager.GetAdminNpub(), - http_url: this.settings.serviceUrl, - lnd_state: WizardTypes.LndState.OFFLINE, - nprofile: this.nprofile, - provider_name: appNamesList, - relay_connected: false, - relays: this.relays, - watchdog_ok: false + try { + const apps = await this.storage.applicationStorage.GetApplications() + const appNamesList = apps.map(app => app.name).join(', ') + const relays = this.settings.nostrRelaySettings ? this.settings.nostrRelaySettings.relays : []; + const relayUrl = (relays && relays.length > 0) ? relays[0] : ''; + const defaultApp = apps.find(a => a.name === this.settings.defaultAppName) || apps[0] + // Determine LND state and watchdog + let lndState: WizardTypes.LndState = WizardTypes.LndState.OFFLINE + let watchdogOk = false + try { + const info = await this.adminManager.LndGetInfo() + const online = info.synced_to_chain && info.synced_to_graph + lndState = online ? WizardTypes.LndState.ONLINE : WizardTypes.LndState.SYNCING + watchdogOk = !info.watchdog_barking + } catch { + lndState = WizardTypes.LndState.OFFLINE + watchdogOk = false + } + return { + admin_npub: this.adminManager.GetAdminNpub(), + http_url: this.settings.serviceUrl, + lnd_state: lndState, + nprofile: this.nprofile, + provider_name: defaultApp?.name || appNamesList, + relay_connected: this.adminManager.GetNostrConnected(), + relays: this.relays, + watchdog_ok: watchdogOk, + source_name: defaultApp?.name || this.settings.defaultAppName || appNamesList, + relay_url: relayUrl, + automate_liquidity: this.settings.liquiditySettings.liquidityProviderPub !== 'null', + push_backups_to_nostr: this.settings.pushBackupsToNostr, + avatar_url: defaultApp?.avatar_url || '', + app_id: defaultApp?.app_id || '' + } + } catch (e) { + this.log(`Error in GetServiceState: ${(e as Error).message}`) + // Return a default/error state that is still valid JSON to prevent client-side parse errors + return { + admin_npub: '', + http_url: '', + lnd_state: WizardTypes.LndState.OFFLINE, + nprofile: '', + provider_name: 'Error loading state', + relay_connected: false, + relays: [], + watchdog_ok: false, + source_name: 'Error', + relay_url: '', + automate_liquidity: false, + push_backups_to_nostr: false, + avatar_url: '', + app_id: '' + } } } + + WizardState = async (): Promise => { return { config_sent: this.pendingConfig !== null, @@ -118,10 +162,30 @@ export class Wizard { relay_url_CustomCheck: relay => relay !== '', }) if (err != null) { throw new Error(err.message) } - if (this.IsInitialized() || this.pendingConfig !== null) { - throw new Error("already initialized") - } const pendingConfig = { sourceName: req.source_name, relayUrl: req.relay_url, automateLiquidity: req.automate_liquidity, pushBackupsToNostr: req.push_backups_to_nostr } + + // Persist app name/avatar to DB regardless (idempotent behavior) + try { + const appsList = await this.storage.applicationStorage.GetApplications() + const defaultNames = ['wallet', 'wallet-test', this.settings.defaultAppName] + const existingDefaultApp = appsList.find(app => defaultNames.includes(app.name)) || appsList[0] + if (existingDefaultApp) { + await this.storage.applicationStorage.UpdateApplication(existingDefaultApp, { name: req.source_name, avatar_url: (req as any).avatar_url || existingDefaultApp.avatar_url }) + } + } catch (e) { + this.log(`Error updating app info: ${(e as Error).message}`) + } + + // If already initialized, treat as idempotent update for env and exit + if (this.IsInitialized()) { + this.updateEnvFile(pendingConfig) + return + } + + // First-time configuration flow + if (this.pendingConfig !== null) { + throw new Error("already initializing") + } this.updateEnvFile(pendingConfig) this.configQueue.forEach(q => q.res(true)) this.configQueue = [] @@ -153,10 +217,16 @@ export class Wizard { } const automateLiquidityIndex = envFileContent.findIndex(line => line.startsWith('LIQUIDITY_PROVIDER_PUB')) - if (automateLiquidityIndex === -1) { - toMerge.push(`LIQUIDITY_PROVIDER_PUB=${pendingConfig.automateLiquidity ? defaultProviderPub : ""}`) + if (pendingConfig.automateLiquidity) { + if (automateLiquidityIndex !== -1) { + envFileContent.splice(automateLiquidityIndex, 1) + } } else { - envFileContent[automateLiquidityIndex] = `LIQUIDITY_PROVIDER_PUB=null` + if (automateLiquidityIndex === -1) { + toMerge.push(`LIQUIDITY_PROVIDER_PUB=null`) + } else { + envFileContent[automateLiquidityIndex] = `LIQUIDITY_PROVIDER_PUB=null` + } } const pushBackupsToNostrIndex = envFileContent.findIndex(line => line.startsWith('PUSH_BACKUPS_TO_NOSTR')) diff --git a/static/backup.html b/static/backup.html deleted file mode 100644 index 14f87bdf..00000000 --- a/static/backup.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - Lightning.Pub - - - - - - -
- Lightning Pub logo - Lightning Pub logo -
- -
-
- -

Choose a Recovery Method

-

- New Node! 🎉 It's important - to backup your keys. -

-
- -
- -
-
-
- In addition to your seed phrase, you also need channel details to recover funds should your node experience a - hardware failure. -
-
-
- It's important always to have the latest version of this file. Fortunately, it's small enough to automatically - store on the Nostr relay. -
-
-
- If you did not choose the developers relay, be sure your relay has - adequate storage policies to hold NIP78 events. -
-
-
- -
- -
-
-
-
- -
- -
-
-
-

-
- -
-
- -
- -
- Need Help? -
- - - - - - \ No newline at end of file diff --git a/static/connect.html b/static/connect.html index 2b4eab16..11251b85 100644 --- a/static/connect.html +++ b/static/connect.html @@ -19,7 +19,7 @@
Lightning Pub logo - Lightning Pub logo + Lightning Pub logo
@@ -48,7 +48,7 @@
Click to reveal
-
+
@@ -104,13 +104,20 @@ height: 157, // correctLevel : QRCode.CorrectLevel.H }); - document.getElementById('connectString').innerHTML = connectString + document.getElementById('connectString').innerText = connectString } } try { fetchInfo() } catch (e) { console.log({ e }) } + // Continue to status + const btn = document.createElement('button') + btn.className = 'push-button' + btn.style.marginTop = '20px' + btn.innerText = 'Continue' + btn.onclick = () => { location.href = 'status.html' } + document.querySelector('.setup-content').appendChild(btn) diff --git a/static/css/connect.css b/static/css/connect.css index 0f7d19e9..16c29174 100644 --- a/static/css/connect.css +++ b/static/css/connect.css @@ -13,9 +13,8 @@ display: block; position: relative; width: 253px; - height: 241px; - -webkit-backdrop-filter: blur(10px); - backdrop-filter: blur(10px); + min-height: 241px; + height: auto; margin-top: 10px; padding: 10px; transition: background-color 0.5s; @@ -26,6 +25,60 @@ transition: background-color 0.3s; } +/* prevent overlap: hide connect string until revealed; click surface on box */ +.qrcode-box #connectString { display: none; margin-top: 6px; } +.qrcode-box.revealed #connectString { display: block; } +.qrcode-box.revealed #connectString { + -webkit-user-select: text !important; + -moz-user-select: text !important; + -ms-user-select: text !important; + user-select: text !important; + pointer-events: auto !important; + position: relative; /* Ensure z-index is respected */ + z-index: 10; /* Bring to the front */ +} +.qrcode-box { cursor: pointer; } +.qrcode-box.revealed { cursor: default; } + +/* QR viewport and veil overlay */ +.qrcode-viewport { + position: relative; + width: 100%; + height: 175px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + margin-top: 6px; +} +.qrcode-viewport #qrcode { + position: absolute; + width: 157px; + height: 157px; +} +/* Remove global QR blur after reveal */ +.qrcode-box.revealed #qrcode { filter: none !important; } + +.qrcode-viewport .qr-veil { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(7px); + -webkit-backdrop-filter: blur(7px); + background: rgba(0,0,0,0.25); + color: #c434e0; + font-size: 12px; + pointer-events: none; /* allow clicks to pass through if needed */ +} +.qrcode-box.revealed .qr-veil { display: none; } +.qrcode-viewport.revealed .qr-veil { display: none; } +.qrcode-box.revealed .qrcode-viewport { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; pointer-events: none; } + +/* hide helper text when revealed to avoid overlap */ +.qrcode-box.revealed #click-text { display: none; } + .qrcode-box-clicked::before { content: ""; position: absolute; diff --git a/static/css/styles.css b/static/css/styles.css index f38597db..cd32fb8d 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -133,6 +133,10 @@ a { display: none; } +.checkbox input[type="radio"] { + display: none; +} + /* Create a new box */ .checkbox label { padding-left: 32px; @@ -157,6 +161,31 @@ a { z-index: -1; } +.checkbox input[type="radio"] + label::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 20px; + height: 20px; + border: 1px solid #a3a3a3; + border-radius: 50%; + background-color: transparent; +} + +.checkbox input[type="radio"]:checked + label::after { + content: ''; + position: absolute; + left: 5px; + top: 50%; + transform: translateY(-50%); + width: 10px; + height: 10px; + border-radius: 50%; + background-color: #a012c7; +} + /* Display a checkmark when the checkbox is checked */ .checkbox input[type="checkbox"]:checked + .checkbox-shape::before { content: "✓"; diff --git a/static/img/LightningPub.png b/static/img/LightningPub.png index 1cbc35de..0e8d1089 100644 Binary files a/static/img/LightningPub.png and b/static/img/LightningPub.png differ diff --git a/static/index.html b/static/index.html index f59b1195..0d8b9a3e 100644 --- a/static/index.html +++ b/static/index.html @@ -8,6 +8,10 @@ + + + + Lightning.Pub @@ -17,45 +21,310 @@
Lightning Pub logo - Lightning Pub logo + Lightning Pub logo
-
-

Setup your Pub

-

-

+
+
+

Setup your Pub

+

+

+
+ +
+ +
+
+ Give this node a name that wallet users will see: + +
+ +
+ Avatar (shown in wallet): +
+ avatar + +
+
+ +
+ If you want to use a specific Nostr relay, enter it now: +
+ +
+ +
+ +
+
+
+ +
+

+
+ + +
-
- -
-
- Give this node a name that wallet users will see: - + + + + + + +
- - +
+
+
Public Node Name:
+
+ +
Nodey McNodeFace
+
+ +
+
+
+
+
Nostr Relay:
+
+ +
wss://relay.lightning.pub
+
+ +
+
+
+
+
Administrator:
+
Loading...
+
+
+
Avatar:
+
+ +
+ avatar +
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+
Continue
+
+
+
+
+
+
Relay Status:
+
Loading...
+
+
+
Lightning Status:
+
Loading...
+
+
+
Watchdog Status: +
+ + + +
+
+
Loading...
+
+
+
+
Guest Invitation Link:
+ +
+
@@ -71,43 +340,10 @@
Need Help? - + + + + \ No newline at end of file diff --git a/static/js/backup.js b/static/js/backup.js deleted file mode 100644 index e9bf36d1..00000000 --- a/static/js/backup.js +++ /dev/null @@ -1,23 +0,0 @@ -$(() => { - let backup; let manual_backup; - $("#backup").click(() => { - backup = $("#backup").prop("checked"); - $('#manual-backup').prop("checked",false); - const nextButton = $("#next-button"); - if (backup) { - nextButton.removeClass("hidden-button"); - } else { - nextButton.addClass("hidden-button"); - } - }); - $("#manual-backup").click(()=>{ - manual_backup = $('#manual-backup').prop("checked"); - $("#backup").prop("checked",false); - const nextButton = $("#next-button"); - if(manual_backup) { - nextButton.removeClass("hidden-button"); - } else { - nextButton.addClass("hidden-button"); - }; - }); -}); diff --git a/static/js/liquidity.js b/static/js/liquidity.js deleted file mode 100644 index c0b653b3..00000000 --- a/static/js/liquidity.js +++ /dev/null @@ -1,19 +0,0 @@ -$(() => { - $("#show-question").click(() => { - $("#question-content").show(); - }); - - $("#close-question").click(() => { - $("#question-content").hide(); - }); - - $("#automate").click(() => { - $('[data-group="service"]').prop("checked", false); - $("#automate").prop("checked", true); - }); - - $("#manual").click(() => { - $('[data-group="service"]').prop("checked", false); - $("#manual").prop("checked", true); - }); -}); diff --git a/static/js/status.js b/static/js/status.js index 24143e78..ff754058 100644 --- a/static/js/status.js +++ b/static/js/status.js @@ -1,4 +1,27 @@ $(() => { + const postConfig = async (updates) => { + try { + const stateRes = await fetch('/wizard/service_state') + if (stateRes.status !== 200) return false + const s = await stateRes.json() + const body = { + source_name: updates.source_name ?? (s.source_name || s.provider_name || ''), + relay_url: updates.relay_url ?? (s.relay_url || (s.relays && s.relays[0]) || ''), + automate_liquidity: s.automate_liquidity || false, + push_backups_to_nostr: s.push_backups_to_nostr || false, + avatar_url: s.avatar_url || '' + } + const res = await fetch('/wizard/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }) + if (res.status !== 200) return false + const j = await res.json().catch(() => ({})) + if (j && j.status && j.status !== 'OK') return false + return true + } catch { return false } + } $("#show-reset").click(() => { $("#reset-content").text('Reset the administrator account if you lost access via the Dashboard.'); $("#reset-box").show(); @@ -11,6 +34,11 @@ $(() => { $("#reset-box").show(); $('.continue-button').attr('id', 'set-show-nostr'); }); + $("#show-avatar").click(() => { + $('.show-avatar').show() + $('#show-avatar-text').hide() + $('input[name="show-avatar"]').focus(); + }); $("#show-nodey").click(() => { $('.show-nodey').show() $('#show-nodey-text').hide() @@ -18,9 +46,13 @@ $(() => { }); $("#save-show-nodey").click(() => { var targetInputVal = $('input[name="show-nodey"]').val() - $('#show-nodey-text').text(targetInputVal) - $('.show-nodey').hide() - $('#show-nodey-text').show() + postConfig({ source_name: targetInputVal }).then(ok => { + if (ok) { + $('#show-nodey-text').text(targetInputVal) + } + $('.show-nodey').hide() + $('#show-nodey-text').show() + }) }) $("#cancel-show-nodey").click(() => { $('.show-nodey').hide() @@ -38,9 +70,28 @@ $(() => { }); $("#save-show-nostr").click(() => { var targetInputVal = $('input[name="show-nostr"]').val() - $('#show-nostr-text').text(targetInputVal) - $('.show-nostr').hide() - $('#show-nostr-text').show() + postConfig({ relay_url: targetInputVal }).then(ok => { + if (ok) { + $('#show-nostr-text').text(targetInputVal) + } + $('.show-nostr').hide() + $('#show-nostr-text').show() + }) + }) + $("#save-show-avatar").click(() => { + var targetInputVal = $('input[name="show-avatar"]').val() + postConfig({ avatar_url: targetInputVal }).then(ok => { + if (ok) { + $('#show-avatar-text').text(targetInputVal || '—') + if (targetInputVal) { $('#avatarImg').attr('src', targetInputVal) } + } + $('.show-avatar').hide() + $('#show-avatar-text').show() + }) + }) + $("#cancel-show-avatar").click(() => { + $('.show-avatar').hide() + $('#show-avatar-text').show() }) $("#cancel-show-nostr").click(() => { $('.show-nostr').hide() diff --git a/static/js/wizard.js b/static/js/wizard.js new file mode 100644 index 00000000..f84736cc --- /dev/null +++ b/static/js/wizard.js @@ -0,0 +1,241 @@ +$(() => { + // Page sections + const pages = { + node: $('#page-node'), + liquidity: $('#page-liquidity'), + backup: $('#page-backup'), + connect: $('#page-connect'), + status: $('#page-status') + }; + + // Inputs + const nodeNameInput = $("#nodeName"); + const relayUrlInput = $("#relayUrl"); + const avatarUrlInput = $("#avatarUrl"); + const avatarPreview = $("#avatarPreview"); + const customCheckbox = $("#customCheckbox"); + const automateLiquidityRadio = $("#automate"); + const manualLiquidityRadio = $("#manual"); + const backupNostrRadio = $("#backup"); + const manualBackupRadio = $("#manual-backup"); + + // Buttons + const toLiquidityBtn = $("#liquidityBtn"); + const toBackupBtn = $("#backupBtn"); + const toStatusBtn = $("#to-status"); + const finishBtn = $("#next-button"); + const backToNodeBtn = $("#back-to-node"); + const backToLiquidityBtn = $("#back-to-liquidity"); + + // Error text + const errorTextNode = $("#errorText"); + const errorTextLiquidity = $("#errorTextLiquidity"); + const errorTextBackup = $("#errorTextBackup"); + + // Liquidity question mark + $("#show-question").click(() => $("#question-content").show()); + $("#close-question").click(() => $("#question-content").hide()); + + const showPage = (pageToShow) => { + Object.values(pages).forEach(page => page.hide()); + pageToShow.show(); + }; + + const populateStatus = async () => { + try { + const res = await fetch('/wizard/service_state'); + if (res.status !== 200) return; + const s = await res.json(); + const name = s.source_name || s.provider_name || ''; + const relay = s.relay_url || (s.relays && s.relays[0]) || ''; + const admin = s.admin_npub || ''; + const avatar = s.avatar_url || (s.app_id ? `https://robohash.org/${encodeURIComponent(s.app_id)}.png?size=128x128&set=set3` : ''); + + const lndState = s.lnd_state; // 0 OFFLINE, 1 SYNCING, 2 ONLINE (per enum) + const watchdogOk = !!s.watchdog_ok; + const relayConnected = !!s.relay_connected; + + $('#show-nodey-text').text(name || '—'); + $('#show-nostr-text').text(relay || '—'); + $('#adminNpub').text(admin || '—'); + if (avatar) { $('#avatarImg').attr('src', avatar); } + + const mkDot = (ok) => ok ? '' : ''; + const lndTxt = lndState === 2 ? 'Online' : (lndState === 1 ? 'Syncing' : 'Offline'); + $('#lndStatus').html(`${mkDot(lndState === 2)} ${lndTxt}`); + $('#watchdog-status').html(`${mkDot(watchdogOk)} ${watchdogOk ? 'OK' : 'Alert'}`); + $('#relayStatus').html(`${mkDot(relayConnected)} ${relayConnected ? 'Connected' : 'Disconnected'}`); + } catch { /* noop */ } + }; + + // Navigation + toLiquidityBtn.click(() => { + const nodeName = nodeNameInput.val(); + const relayUrl = relayUrlInput.val(); + const useDefaultRelay = customCheckbox.prop('checked'); + if (!nodeName) { + errorTextNode.text("Please enter a node name"); + return; + } + if (!useDefaultRelay && !relayUrl) { + errorTextNode.text("Please enter a relay URL or check the default relay box"); + return; + } + errorTextNode.text(""); + showPage(pages.liquidity); + }); + + toBackupBtn.click(() => { + if (!automateLiquidityRadio.prop('checked') && !manualLiquidityRadio.prop('checked')) { + errorTextLiquidity.text('Please select an option'); + return; + } + errorTextLiquidity.text(""); + showPage(pages.backup); + }); + + backToNodeBtn.click(() => showPage(pages.node)); + backToLiquidityBtn.click(() => showPage(pages.liquidity)); + + // Final submission + finishBtn.click(async () => { + if (!backupNostrRadio.prop('checked') && !manualBackupRadio.prop('checked')) { + errorTextBackup.text('Please select an option'); + return; + } + errorTextBackup.text(""); + + const relayUrl = customCheckbox.prop('checked') ? 'wss://relay.lightning.pub' : relayUrlInput.val(); + + const req = { + source_name: nodeNameInput.val(), + relay_url: relayUrl, + automate_liquidity: automateLiquidityRadio.prop('checked'), + push_backups_to_nostr: backupNostrRadio.prop('checked'), + avatar_url: avatarUrlInput.val() + }; + + try { + const res = await fetch("/wizard/config", { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(req) + }); + if (!res.ok) { + const j = await res.json(); + throw new Error(j.reason || "Failed to start service"); + } + // Move to in-page connect step + showPage(pages.connect); + // fetch and prepare connect info + (async () => { + const res = await fetch('/wizard/admin_connect_info'); + if (res.status !== 200) return; + const j = await res.json(); + if (j.connect_info && j.connect_info.enrolled_npub) { + showPage(pages.status); + await populateStatus(); + return; + } + const connectString = j.nprofile + ':' + j.connect_info.admin_token; + const qrElement = document.getElementById('qrcode'); + const codebox = $('#codebox'); + const clickText = $('#click-text'); + const cs = $('#connectString'); + + // Reset visual state + codebox.removeClass('revealed'); + cs.text(''); + codebox.find('.qr-veil').show(); + clickText.show(); + if (qrElement) { + while (qrElement.firstChild) qrElement.removeChild(qrElement.firstChild); + // Pre-generate QR behind veil to entice reveal + new QRCode(qrElement, { text: connectString, colorDark: '#000000', colorLight: '#ffffff', width: 157, height: 157 }); + } + + // Reveal on click: show string below and remove veil/heading + codebox.off('click').on('click', (e) => { + if (!codebox.hasClass('revealed')) { + e.preventDefault(); + e.stopPropagation(); + cs.text(connectString); + codebox.addClass('revealed'); + + // Remove the veil from the DOM entirely to kill the blur + codebox.find('.qr-veil').remove(); + + clickText.hide(); + + // Unbind to allow text selection and normal behavior after reveal + codebox.off('click'); + + // Force text to be selectable on top + cs.css({ + 'user-select': 'text', + '-webkit-user-select': 'text', + 'pointer-events': 'auto' + }); + } + }); + })(); + } catch (err) { + errorTextBackup.text(err.message); + } + }); + + // Navigate from connect to status + toStatusBtn && toStatusBtn.click(async () => { + showPage(pages.status); + await populateStatus(); + }) + + const syncRelayState = () => { + relayUrlInput.prop('disabled', customCheckbox.prop('checked')); + if (customCheckbox.prop('checked')) { + relayUrlInput.val(''); + } + }; + + customCheckbox.on('change', syncRelayState); + relayUrlInput.on('input', () => { + if (relayUrlInput.val()) { + customCheckbox.prop('checked', false); + syncRelayState(); + } + }); + + // Initial state load (no redirects; SPA only) + console.log('Wizard script version: REVEAL_FIX_3 activated'); + fetch("/wizard/service_state").then(res => res.json()).then(state => { + nodeNameInput.val(state.source_name); + if (state.relay_url === 'wss://relay.lightning.pub') { + customCheckbox.prop('checked', true); + } else { + relayUrlInput.val(state.relay_url); + } + const robo = state.app_id ? `https://robohash.org/${encodeURIComponent(state.app_id)}.png?size=128x128&set=set3` : '' + if (state.avatar_url) { + avatarUrlInput.val(state.avatar_url); + avatarPreview.attr('src', state.avatar_url) + } else if (robo) { + avatarPreview.attr('src', robo) + } + if (robo) { + avatarUrlInput.attr('placeholder', robo) + } + syncRelayState(); + + if (state.automate_liquidity) { + automateLiquidityRadio.prop('checked', true); + } else { + manualLiquidityRadio.prop('checked', true); + } + + if (state.push_backups_to_nostr) { + backupNostrRadio.prop('checked', true); + } else { + manualBackupRadio.prop('checked', true); + } + }); +}); diff --git a/static/liquidity.html b/static/liquidity.html deleted file mode 100644 index 253f541b..00000000 --- a/static/liquidity.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - Lightning.Pub - - - - - - -
- Lightning Pub logo - Lightning Pub logo -
- -
-
- -

Manage Node Liquidity

-

- How do you want to manage Lightning channels? -

-
- -
- -
-
- -
- -
-
- -
- -
-
-

-
- -
-
- -
- -
- Need Help? -
- - - - - \ No newline at end of file diff --git a/static/status.html b/static/status.html index 44210dc6..5ca1f4ec 100644 --- a/static/status.html +++ b/static/status.html @@ -19,7 +19,7 @@
Lightning Pub logo - Lightning Pub logo + Lightning Pub logo
@@ -69,6 +69,26 @@ +
+
Avatar:
+
+ +
+ avatar +
+ +
+
+
+
Administrator:
@@ -157,20 +177,29 @@ } const j = await res.json() console.log(j) - if (j.status !== 'OK') { - document.getElementById('errorText').innerText = "failed to get state info" + j.reason - return - } - document.getElementById("show-nodey-text").innerHTML = j.provider_name - document.getElementById("show-nostr-text").innerHTML = j.relays[0] - document.getElementById("adminNpub").innerText = j.admin_npub - document.getElementById("relayStatus").innerHTML = ` ${j.relay_connected ? 'Connected' : 'Disconnected'}` - document.getElementById("lndStatus").innerHTML = ` ${j.lnd_state}` + // Map fields from service state + const name = j.source_name || j.provider_name || 'Unknown' + const relayUrl = j.relay_url || (j.relays && j.relays[0]) || '' + const admin = j.admin_npub || '' + // lnd_state may arrive as enum number or string; normalize to text + const lndText = (typeof j.lnd_state === 'string') + ? j.lnd_state + : (j.lnd_state === 2 ? 'ONLINE' : (j.lnd_state === 1 ? 'SYNCING' : 'OFFLINE')) + const lndDot = (lndText === 'ONLINE') ? 'green-dot' : (lndText === 'SYNCING' ? 'yellow-dot' : 'red-dot') + // use actual relay_connected flag from backend + const relayConnected = !!j.relay_connected + const avatar = j.avatar_url || (j.app_id ? `https://robohash.org/${encodeURIComponent(j.app_id)}.png?size=128x128&set=set3` : '') + document.getElementById("show-nodey-text").innerHTML = name + document.getElementById("show-nostr-text").innerHTML = relayUrl || '—' + if (avatar) { document.getElementById("avatarImg").src = avatar } + document.getElementById("adminNpub").innerText = admin + document.getElementById("relayStatus").innerHTML = ` ${relayConnected ? 'Connected' : 'Disconnected'}` + document.getElementById("lndStatus").innerHTML = ` ${lndText}` document.getElementById("watchdog-status").innerHTML = ` ${j.watchdog_ok ? 'No Alerts' : 'ALERT!!'}` document.getElementById("inviteLinkHttp").href = `https://my.shockwallet.app/#/sources?addSource=${j.nprofile}` document.getElementById("inviteLinkHttp").innerHTML = `https://my.shockwallet.app/#/sources?addSource=${j.nprofile}` - document.querySelector('input[name="show-nodey"]').placeholder = j.provider_name; - document.title = j.provider_name; + document.querySelector('input[name="show-nodey"]').placeholder = name; + document.title = name; } try { fetchInfo()