Merge pull request #702 from shocknet/localhost-screens
Localhost screens
This commit is contained in:
commit
0e0b7f75ce
44 changed files with 7243 additions and 5165 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -13,4 +13,6 @@ logs
|
|||
.jwt_secret
|
||||
data/
|
||||
.wallet_secret
|
||||
.wallet_password
|
||||
.wallet_password
|
||||
.admin_enroll
|
||||
admin.npub
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
create lnd classes: `npx protoc -I ./others --ts_out=./lnd others/*`
|
||||
create server classes: `npx protoc -I ./service --pub_out=. service/*`
|
||||
create wizard classes: `npx protoc -I ./wizard --pub_out=./wizard_service wizard/*`
|
||||
|
||||
export PATH=$PATH:~/Lightning.Pub/proto
|
||||
|
|
@ -13,11 +13,51 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
## NOSTR Methods
|
||||
### These are the nostr methods the client implements to communicate with the API via nostr
|
||||
|
||||
- LndGetInfo
|
||||
- auth type: __Admin__
|
||||
- input: [LndGetInfoRequest](#LndGetInfoRequest)
|
||||
- output: [LndGetInfoResponse](#LndGetInfoResponse)
|
||||
|
||||
- AddApp
|
||||
- auth type: __Admin__
|
||||
- input: [AddAppRequest](#AddAppRequest)
|
||||
- output: [AuthApp](#AuthApp)
|
||||
|
||||
- AuthApp
|
||||
- auth type: __Admin__
|
||||
- input: [AuthAppRequest](#AuthAppRequest)
|
||||
- output: [AuthApp](#AuthApp)
|
||||
|
||||
- BanUser
|
||||
- auth type: __Admin__
|
||||
- input: [BanUserRequest](#BanUserRequest)
|
||||
- output: [BanUserResponse](#BanUserResponse)
|
||||
|
||||
- GetUsageMetrics
|
||||
- auth type: __Metrics__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [UsageMetrics](#UsageMetrics)
|
||||
|
||||
- GetAppsMetrics
|
||||
- auth type: __Metrics__
|
||||
- input: [AppsMetricsRequest](#AppsMetricsRequest)
|
||||
- output: [AppsMetrics](#AppsMetrics)
|
||||
|
||||
- GetLndMetrics
|
||||
- auth type: __Metrics__
|
||||
- input: [LndMetricsRequest](#LndMetricsRequest)
|
||||
- output: [LndMetrics](#LndMetrics)
|
||||
|
||||
- LinkNPubThroughToken
|
||||
- auth type: __User__
|
||||
- input: [LinkNPubThroughTokenRequest](#LinkNPubThroughTokenRequest)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- EnrollAdminToken
|
||||
- auth type: __User__
|
||||
- input: [EnrollAdminTokenRequest](#EnrollAdminTokenRequest)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- UserHealth
|
||||
- auth type: __User__
|
||||
- This methods has an __empty__ __request__ body
|
||||
|
|
@ -120,9 +160,9 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
|
||||
- __User__:
|
||||
- expected context content
|
||||
- __user_id__: _string_
|
||||
- __app_id__: _string_
|
||||
- __app_user_id__: _string_
|
||||
- __user_id__: _string_
|
||||
|
||||
- __Admin__:
|
||||
- expected context content
|
||||
|
|
@ -265,6 +305,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [LinkNPubThroughTokenRequest](#LinkNPubThroughTokenRequest)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- EnrollAdminToken
|
||||
- auth type: __User__
|
||||
- http method: __post__
|
||||
- http route: __/api/guest/npub/enroll/admin__
|
||||
- input: [EnrollAdminTokenRequest](#EnrollAdminTokenRequest)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- GetApp
|
||||
- auth type: __App__
|
||||
- http method: __post__
|
||||
|
|
@ -482,47 +529,36 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
## Messages
|
||||
### The content of requests and response from the methods
|
||||
|
||||
### LnurlLinkResponse
|
||||
- __lnurl__: _string_
|
||||
- __k1__: _string_
|
||||
### Empty
|
||||
|
||||
### RelaysMigration
|
||||
- __relays__: ARRAY of: _string_
|
||||
|
||||
### RequestNPubLinkingTokenResponse
|
||||
- __token__: _string_
|
||||
|
||||
### BannedAppUser
|
||||
- __app_name__: _string_
|
||||
- __app_id__: _string_
|
||||
- __user_identifier__: _string_
|
||||
- __nostr_pub__: _string_
|
||||
|
||||
### Application
|
||||
- __name__: _string_
|
||||
- __id__: _string_
|
||||
- __balance__: _number_
|
||||
- __npub__: _string_
|
||||
|
||||
### PayInvoiceResponse
|
||||
- __preimage__: _string_
|
||||
- __amount_paid__: _number_
|
||||
- __operation_id__: _string_
|
||||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
|
||||
### OpenChannelRequest
|
||||
- __destination__: _string_
|
||||
- __fundingAmount__: _number_
|
||||
- __pushAmount__: _number_
|
||||
- __closeAddress__: _string_
|
||||
|
||||
### LndMetrics
|
||||
- __nodes__: ARRAY of: _[LndNodeMetrics](#LndNodeMetrics)_
|
||||
### LndMetricsRequest
|
||||
- __from_unix__: _number_ *this field is optional
|
||||
- __to_unix__: _number_ *this field is optional
|
||||
|
||||
### LndGetInfoResponse
|
||||
- __alias__: _string_
|
||||
|
||||
### PayAppUserInvoiceRequest
|
||||
- __user_identifier__: _string_
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### Product
|
||||
- __id__: _string_
|
||||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
|
||||
### LndGetInfoRequest
|
||||
- __nodeId__: _number_
|
||||
|
||||
### BanUserResponse
|
||||
- __balance_sats__: _number_
|
||||
- __banned_app_users__: ARRAY of: _[BannedAppUser](#BannedAppUser)_
|
||||
|
||||
### AuthAppRequest
|
||||
- __name__: _string_
|
||||
- __allow_user_creation__: _boolean_ *this field is optional
|
||||
|
||||
### PayAddressRequest
|
||||
- __address__: _string_
|
||||
- __amoutSats__: _number_
|
||||
|
|
@ -533,69 +569,94 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __to_unix__: _number_ *this field is optional
|
||||
- __include_operations__: _boolean_ *this field is optional
|
||||
|
||||
### AppsMetrics
|
||||
- __apps__: ARRAY of: _[AppMetrics](#AppMetrics)_
|
||||
|
||||
### RoutingEvent
|
||||
- __timestamp_ns__: _number_
|
||||
- __event_type__: _string_
|
||||
- __settled__: _boolean_
|
||||
- __offchain__: _boolean_
|
||||
- __incoming_channel_id__: _number_
|
||||
- __incoming_htlc_id__: _number_
|
||||
- __outgoing_channel_id__: _number_
|
||||
- __outgoing_htlc_id__: _number_
|
||||
- __timestamp_ns__: _number_
|
||||
- __event_type__: _string_
|
||||
- __forward_fail_event__: _boolean_
|
||||
- __incoming_amt_msat__: _number_
|
||||
- __outgoing_amt_msat__: _number_
|
||||
- __failure_string__: _string_
|
||||
- __settled__: _boolean_
|
||||
- __offchain__: _boolean_
|
||||
- __forward_fail_event__: _boolean_
|
||||
|
||||
### ClosedChannel
|
||||
- __channel_id__: _string_
|
||||
- __capacity__: _number_
|
||||
- __closed_height__: _number_
|
||||
### BannedAppUser
|
||||
- __app_name__: _string_
|
||||
- __app_id__: _string_
|
||||
- __user_identifier__: _string_
|
||||
- __nostr_pub__: _string_
|
||||
|
||||
### AddAppRequest
|
||||
### GetUserOperationsResponse
|
||||
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
||||
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
||||
|
||||
### RequestNPubLinkingTokenResponse
|
||||
- __token__: _string_
|
||||
|
||||
### UsageMetric
|
||||
- __processed_at_ms__: _number_
|
||||
- __auth_in_nano__: _number_
|
||||
- __rpc_name__: _string_
|
||||
- __nostr__: _boolean_
|
||||
- __batch_size__: _number_
|
||||
- __parsed_in_nano__: _number_
|
||||
- __validate_in_nano__: _number_
|
||||
- __handle_in_nano__: _number_
|
||||
- __batch__: _boolean_
|
||||
|
||||
### NewInvoiceRequest
|
||||
- __amountSats__: _number_
|
||||
- __memo__: _string_
|
||||
|
||||
### OpenChannelRequest
|
||||
- __destination__: _string_
|
||||
- __fundingAmount__: _number_
|
||||
- __pushAmount__: _number_
|
||||
- __closeAddress__: _string_
|
||||
|
||||
### ChainBalanceEvent
|
||||
- __block_height__: _number_
|
||||
- __confirmed_balance__: _number_
|
||||
- __unconfirmed_balance__: _number_
|
||||
- __total_balance__: _number_
|
||||
|
||||
### GetAppUserRequest
|
||||
- __user_identifier__: _string_
|
||||
|
||||
### NewAddressResponse
|
||||
- __address__: _string_
|
||||
|
||||
### PayInvoiceResponse
|
||||
- __preimage__: _string_
|
||||
- __amount_paid__: _number_
|
||||
- __operation_id__: _string_
|
||||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
|
||||
### AddProductRequest
|
||||
- __name__: _string_
|
||||
- __allow_user_creation__: _boolean_
|
||||
- __price_sats__: _number_
|
||||
|
||||
### MigrationUpdate
|
||||
- __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional
|
||||
- __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional
|
||||
|
||||
### ClosureMigration
|
||||
- __closes_at_unix__: _number_
|
||||
|
||||
### BanUserResponse
|
||||
- __balance_sats__: _number_
|
||||
- __banned_app_users__: ARRAY of: _[BannedAppUser](#BannedAppUser)_
|
||||
|
||||
### AddAppUserRequest
|
||||
- __identifier__: _string_
|
||||
- __fail_if_exists__: _boolean_
|
||||
- __balance__: _number_
|
||||
|
||||
### SetMockAppUserBalanceRequest
|
||||
- __user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### PayInvoiceRequest
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### LnurlWithdrawInfoResponse
|
||||
- __tag__: _string_
|
||||
- __callback__: _string_
|
||||
- __k1__: _string_
|
||||
- __defaultDescription__: _string_
|
||||
- __minWithdrawable__: _number_
|
||||
- __maxWithdrawable__: _number_
|
||||
- __balanceCheck__: _string_
|
||||
- __payLink__: _string_
|
||||
|
||||
### UsageMetric
|
||||
- __processed_at_ms__: _number_
|
||||
- __parsed_in_nano__: _number_
|
||||
- __auth_in_nano__: _number_
|
||||
- __validate_in_nano__: _number_
|
||||
- __handle_in_nano__: _number_
|
||||
- __rpc_name__: _string_
|
||||
- __batch__: _boolean_
|
||||
- __nostr__: _boolean_
|
||||
- __batch_size__: _number_
|
||||
### EncryptionExchangeRequest
|
||||
- __publicKey__: _string_
|
||||
- __deviceId__: _string_
|
||||
|
||||
### UsersInfo
|
||||
- __total__: _number_
|
||||
|
|
@ -605,207 +666,70 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __balance_avg__: _number_
|
||||
- __balance_median__: _number_
|
||||
|
||||
### BanUserRequest
|
||||
- __user_id__: _string_
|
||||
|
||||
### UserOperations
|
||||
- __fromIndex__: _number_
|
||||
- __toIndex__: _number_
|
||||
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
|
||||
|
||||
### RequestNPubLinkingTokenRequest
|
||||
- __user_identifier__: _string_
|
||||
|
||||
### UserOperation
|
||||
- __paidAtUnix__: _number_
|
||||
- __type__: _[UserOperationType](#UserOperationType)_
|
||||
- __inbound__: _boolean_
|
||||
- __amount__: _number_
|
||||
- __identifier__: _string_
|
||||
- __operationId__: _string_
|
||||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
- __confirmed__: _boolean_
|
||||
- __tx_hash__: _string_
|
||||
- __internal__: _boolean_
|
||||
|
||||
### Product
|
||||
- __id__: _string_
|
||||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
|
||||
### LinkNPubThroughTokenRequest
|
||||
- __token__: _string_
|
||||
- __nostr_pub__: _string_
|
||||
|
||||
### EncryptionExchangeRequest
|
||||
- __publicKey__: _string_
|
||||
- __deviceId__: _string_
|
||||
|
||||
### PayAppUserInvoiceRequest
|
||||
- __user_identifier__: _string_
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### SendAppUserToAppPaymentRequest
|
||||
- __from_user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### SendAppUserToAppUserPaymentRequest
|
||||
- __from_user_identifier__: _string_
|
||||
- __to_user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### NewInvoiceRequest
|
||||
- __amountSats__: _number_
|
||||
- __memo__: _string_
|
||||
|
||||
### MigrationUpdate
|
||||
- __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional
|
||||
- __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional
|
||||
|
||||
### GetUserOperationsResponse
|
||||
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
||||
|
||||
### AddAppUserInvoiceRequest
|
||||
- __receiver_identifier__: _string_
|
||||
- __payer_identifier__: _string_
|
||||
- __http_callback_url__: _string_
|
||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
||||
|
||||
### DecodeInvoiceRequest
|
||||
- __invoice__: _string_
|
||||
|
||||
### UserInfo
|
||||
- __userId__: _string_
|
||||
- __balance__: _number_
|
||||
- __max_withdrawable__: _number_
|
||||
- __user_identifier__: _string_
|
||||
- __service_fee_bps__: _number_
|
||||
- __network_max_fee_bps__: _number_
|
||||
- __network_max_fee_fixed__: _number_
|
||||
|
||||
### DecodeInvoiceResponse
|
||||
- __amount__: _number_
|
||||
|
||||
### OpenChannelResponse
|
||||
- __channelId__: _string_
|
||||
|
||||
### LiveUserOperation
|
||||
- __operation__: _[UserOperation](#UserOperation)_
|
||||
|
||||
### UsageMetrics
|
||||
- __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_
|
||||
|
||||
### AuthApp
|
||||
- __app__: _[Application](#Application)_
|
||||
- __auth_token__: _string_
|
||||
|
||||
### AddAppInvoiceRequest
|
||||
- __payer_identifier__: _string_
|
||||
- __http_callback_url__: _string_
|
||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
||||
|
||||
### HandleLnurlPayResponse
|
||||
- __pr__: _string_
|
||||
- __routes__: ARRAY of: _[Empty](#Empty)_
|
||||
|
||||
### Empty
|
||||
|
||||
### ChannelBalanceEvent
|
||||
- __block_height__: _number_
|
||||
- __channel_id__: _string_
|
||||
- __local_balance_sats__: _number_
|
||||
- __remote_balance_sats__: _number_
|
||||
|
||||
### GetAppUserRequest
|
||||
- __user_identifier__: _string_
|
||||
### ChannelRouting
|
||||
- __receive_errors__: _number_
|
||||
- __forward_errors_as_output__: _number_
|
||||
- __missed_forward_fee_as_output__: _number_
|
||||
- __events_number__: _number_
|
||||
- __channel_id__: _string_
|
||||
- __forward_errors_as_input__: _number_
|
||||
- __missed_forward_fee_as_input__: _number_
|
||||
- __forward_fee_as_input__: _number_
|
||||
- __forward_fee_as_output__: _number_
|
||||
- __send_errors__: _number_
|
||||
|
||||
### AddProductRequest
|
||||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
|
||||
### AppUser
|
||||
- __identifier__: _string_
|
||||
- __info__: _[UserInfo](#UserInfo)_
|
||||
- __max_withdrawable__: _number_
|
||||
|
||||
### GetAppUserLNURLInfoRequest
|
||||
- __user_identifier__: _string_
|
||||
- __base_url_override__: _string_
|
||||
### SendAppUserToAppPaymentRequest
|
||||
- __from_user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### SetMockAppBalanceRequest
|
||||
- __amount__: _number_
|
||||
|
||||
### NewAddressRequest
|
||||
- __addressType__: _[AddressType](#AddressType)_
|
||||
|
||||
### GetProductBuyLinkResponse
|
||||
- __link__: _string_
|
||||
|
||||
### HttpCreds
|
||||
- __url__: _string_
|
||||
- __token__: _string_
|
||||
|
||||
### AppMetrics
|
||||
- __app__: _[Application](#Application)_
|
||||
- __users__: _[UsersInfo](#UsersInfo)_
|
||||
- __received__: _number_
|
||||
- __spent__: _number_
|
||||
- __available__: _number_
|
||||
- __fees__: _number_
|
||||
- __invoices__: _number_
|
||||
- __total_fees__: _number_
|
||||
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
|
||||
### ClosedChannel
|
||||
- __channel_id__: _string_
|
||||
- __capacity__: _number_
|
||||
- __closed_height__: _number_
|
||||
|
||||
### ChainBalanceEvent
|
||||
- __block_height__: _number_
|
||||
- __confirmed_balance__: _number_
|
||||
- __unconfirmed_balance__: _number_
|
||||
- __total_balance__: _number_
|
||||
### Application
|
||||
- __id__: _string_
|
||||
- __balance__: _number_
|
||||
- __npub__: _string_
|
||||
- __name__: _string_
|
||||
|
||||
### SetMockInvoiceAsPaidRequest
|
||||
- __invoice__: _string_
|
||||
### AddAppUserRequest
|
||||
- __balance__: _number_
|
||||
- __identifier__: _string_
|
||||
- __fail_if_exists__: _boolean_
|
||||
|
||||
### SetMockAppUserBalanceRequest
|
||||
- __user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### LndGetInfoRequest
|
||||
- __nodeId__: _number_
|
||||
|
||||
### NewAddressResponse
|
||||
- __address__: _string_
|
||||
|
||||
### PayAddressResponse
|
||||
- __txId__: _string_
|
||||
- __operation_id__: _string_
|
||||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
- __txId__: _string_
|
||||
|
||||
### AppsMetrics
|
||||
- __apps__: ARRAY of: _[AppMetrics](#AppMetrics)_
|
||||
### NewInvoiceResponse
|
||||
- __invoice__: _string_
|
||||
|
||||
### LndNodeMetrics
|
||||
- __channels_balance_events__: ARRAY of: _[ChannelBalanceEvent](#ChannelBalanceEvent)_
|
||||
- __chain_balance_events__: ARRAY of: _[ChainBalanceEvent](#ChainBalanceEvent)_
|
||||
- __offline_channels__: _number_
|
||||
- __online_channels__: _number_
|
||||
- __pending_channels__: _number_
|
||||
- __closing_channels__: _number_
|
||||
- __open_channels__: ARRAY of: _[OpenChannel](#OpenChannel)_
|
||||
- __closed_channels__: ARRAY of: _[ClosedChannel](#ClosedChannel)_
|
||||
- __channel_routing__: ARRAY of: _[ChannelRouting](#ChannelRouting)_
|
||||
### RequestNPubLinkingTokenRequest
|
||||
- __user_identifier__: _string_
|
||||
|
||||
### AuthAppRequest
|
||||
- __name__: _string_
|
||||
- __allow_user_creation__: _boolean_ *this field is optional
|
||||
|
||||
### LndMetricsRequest
|
||||
- __from_unix__: _number_ *this field is optional
|
||||
- __to_unix__: _number_ *this field is optional
|
||||
### LinkNPubThroughTokenRequest
|
||||
- __token__: _string_
|
||||
- __nostr_pub__: _string_
|
||||
|
||||
### OpenChannel
|
||||
- __channel_id__: _string_
|
||||
|
|
@ -815,38 +739,164 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __local_balance__: _number_
|
||||
- __remote_balance__: _number_
|
||||
|
||||
### NewInvoiceResponse
|
||||
### SetMockInvoiceAsPaidRequest
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### AddAppUserInvoiceRequest
|
||||
- __receiver_identifier__: _string_
|
||||
- __payer_identifier__: _string_
|
||||
- __http_callback_url__: _string_
|
||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
||||
|
||||
### DecodeInvoiceResponse
|
||||
- __amount__: _number_
|
||||
|
||||
### OpenChannelResponse
|
||||
- __channelId__: _string_
|
||||
|
||||
### RelaysMigration
|
||||
- __relays__: ARRAY of: _string_
|
||||
|
||||
### LndMetrics
|
||||
- __nodes__: ARRAY of: _[LndNodeMetrics](#LndNodeMetrics)_
|
||||
|
||||
### AppUser
|
||||
- __identifier__: _string_
|
||||
- __info__: _[UserInfo](#UserInfo)_
|
||||
- __max_withdrawable__: _number_
|
||||
|
||||
### AddAppInvoiceRequest
|
||||
- __payer_identifier__: _string_
|
||||
- __http_callback_url__: _string_
|
||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
||||
|
||||
### UserOperations
|
||||
- __fromIndex__: _number_
|
||||
- __toIndex__: _number_
|
||||
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
|
||||
|
||||
### BanUserRequest
|
||||
- __user_id__: _string_
|
||||
|
||||
### NewAddressRequest
|
||||
- __addressType__: _[AddressType](#AddressType)_
|
||||
|
||||
### DecodeInvoiceRequest
|
||||
- __invoice__: _string_
|
||||
|
||||
### ChannelRouting
|
||||
- __channel_id__: _string_
|
||||
- __send_errors__: _number_
|
||||
- __receive_errors__: _number_
|
||||
- __forward_errors_as_input__: _number_
|
||||
- __forward_errors_as_output__: _number_
|
||||
- __missed_forward_fee_as_input__: _number_
|
||||
- __missed_forward_fee_as_output__: _number_
|
||||
- __forward_fee_as_input__: _number_
|
||||
- __forward_fee_as_output__: _number_
|
||||
- __events_number__: _number_
|
||||
|
||||
### LnurlPayInfoResponse
|
||||
- __tag__: _string_
|
||||
### LnurlWithdrawInfoResponse
|
||||
- __callback__: _string_
|
||||
- __maxSendable__: _number_
|
||||
- __minSendable__: _number_
|
||||
- __metadata__: _string_
|
||||
- __allowsNostr__: _boolean_
|
||||
- __nostrPubkey__: _string_
|
||||
- __k1__: _string_
|
||||
- __defaultDescription__: _string_
|
||||
- __minWithdrawable__: _number_
|
||||
- __maxWithdrawable__: _number_
|
||||
- __balanceCheck__: _string_
|
||||
- __payLink__: _string_
|
||||
- __tag__: _string_
|
||||
|
||||
### GetUserOperationsRequest
|
||||
- __latestIncomingInvoice__: _number_
|
||||
- __latestOutgoingInvoice__: _number_
|
||||
- __latestIncomingTx__: _number_
|
||||
- __latestOutgoingTx__: _number_
|
||||
- __latestIncomingUserToUserPayment__: _number_
|
||||
- __latestOutgoingUserToUserPayment__: _number_
|
||||
- __max_size__: _number_
|
||||
- __latestIncomingInvoice__: _number_
|
||||
|
||||
### AppMetrics
|
||||
- __spent__: _number_
|
||||
- __available__: _number_
|
||||
- __fees__: _number_
|
||||
- __invoices__: _number_
|
||||
- __total_fees__: _number_
|
||||
- __app__: _[Application](#Application)_
|
||||
- __users__: _[UsersInfo](#UsersInfo)_
|
||||
- __received__: _number_
|
||||
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
|
||||
|
||||
### AddAppRequest
|
||||
- __name__: _string_
|
||||
- __allow_user_creation__: _boolean_
|
||||
|
||||
### SendAppUserToAppUserPaymentRequest
|
||||
- __amount__: _number_
|
||||
- __from_user_identifier__: _string_
|
||||
- __to_user_identifier__: _string_
|
||||
|
||||
### LnurlPayInfoResponse
|
||||
- __callback__: _string_
|
||||
- __maxSendable__: _number_
|
||||
- __minSendable__: _number_
|
||||
- __metadata__: _string_
|
||||
- __allowsNostr__: _boolean_
|
||||
- __nostrPubkey__: _string_
|
||||
- __tag__: _string_
|
||||
|
||||
### HandleLnurlPayResponse
|
||||
- __pr__: _string_
|
||||
- __routes__: ARRAY of: _[Empty](#Empty)_
|
||||
|
||||
### EnrollAdminTokenRequest
|
||||
- __admin_token__: _string_
|
||||
|
||||
### UsageMetrics
|
||||
- __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_
|
||||
|
||||
### LnurlLinkResponse
|
||||
- __k1__: _string_
|
||||
- __lnurl__: _string_
|
||||
|
||||
### LiveUserOperation
|
||||
- __operation__: _[UserOperation](#UserOperation)_
|
||||
|
||||
### PayInvoiceRequest
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### GetProductBuyLinkResponse
|
||||
- __link__: _string_
|
||||
|
||||
### LndNodeMetrics
|
||||
- __chain_balance_events__: ARRAY of: _[ChainBalanceEvent](#ChainBalanceEvent)_
|
||||
- __online_channels__: _number_
|
||||
- __closing_channels__: _number_
|
||||
- __channels_balance_events__: ARRAY of: _[ChannelBalanceEvent](#ChannelBalanceEvent)_
|
||||
- __pending_channels__: _number_
|
||||
- __open_channels__: ARRAY of: _[OpenChannel](#OpenChannel)_
|
||||
- __closed_channels__: ARRAY of: _[ClosedChannel](#ClosedChannel)_
|
||||
- __channel_routing__: ARRAY of: _[ChannelRouting](#ChannelRouting)_
|
||||
- __offline_channels__: _number_
|
||||
|
||||
### AuthApp
|
||||
- __app__: _[Application](#Application)_
|
||||
- __auth_token__: _string_
|
||||
|
||||
### GetAppUserLNURLInfoRequest
|
||||
- __user_identifier__: _string_
|
||||
- __base_url_override__: _string_
|
||||
|
||||
### UserInfo
|
||||
- __network_max_fee_fixed__: _number_
|
||||
- __userId__: _string_
|
||||
- __balance__: _number_
|
||||
- __max_withdrawable__: _number_
|
||||
- __user_identifier__: _string_
|
||||
- __service_fee_bps__: _number_
|
||||
- __network_max_fee_bps__: _number_
|
||||
|
||||
### UserOperation
|
||||
- __paidAtUnix__: _number_
|
||||
- __service_fee__: _number_
|
||||
- __confirmed__: _boolean_
|
||||
- __tx_hash__: _string_
|
||||
- __internal__: _boolean_
|
||||
- __network_fee__: _number_
|
||||
- __type__: _[UserOperationType](#UserOperationType)_
|
||||
- __inbound__: _boolean_
|
||||
- __amount__: _number_
|
||||
- __identifier__: _string_
|
||||
- __operationId__: _string_
|
||||
## Enums
|
||||
### The enumerators used in the messages
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -364,6 +364,28 @@ 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.EnrollAdminToken) throw new Error('method: EnrollAdminToken is not implemented')
|
||||
app.post('/api/guest/npub/enroll/admin', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'EnrollAdminToken', 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.EnrollAdminToken) throw new Error('method: EnrollAdminToken is not implemented')
|
||||
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
|
||||
authCtx = authContext
|
||||
stats.guard = process.hrtime.bigint()
|
||||
const request = req.body
|
||||
const error = Types.EnrollAdminTokenRequestValidate(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.EnrollAdminToken({rpcName:'EnrollAdminToken', 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.GetApp) throw new Error('method: GetApp is not implemented')
|
||||
app.post('/api/app/get', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'GetApp', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
@ -948,6 +970,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'EnrollAdminToken':
|
||||
if (!methods.EnrollAdminToken) {
|
||||
throw new Error('method EnrollAdminToken not found' )
|
||||
} else {
|
||||
const error = Types.EnrollAdminTokenRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
await methods.EnrollAdminToken({...operation, ctx}); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'UserHealth':
|
||||
if (!methods.UserHealth) {
|
||||
throw new Error('method UserHealth not found' )
|
||||
|
|
@ -1121,6 +1155,6 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
var server: { close: () => void } | undefined
|
||||
return {
|
||||
Close: () => { if (!server) { throw new Error('tried closing server before starting') } else server.close() },
|
||||
Listen: (port: number) => { server = app.listen(port, () => logger.log('Example app listening on port ' + port)) }
|
||||
Listen: (port: number) => { server = app.listen(port, () => logger.log('LightningPub listening on port ' + port)) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,6 +234,17 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
EnrollAdminToken: async (request: Types.EnrollAdminTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveUserAuth()
|
||||
if (auth === null) throw new Error('retrieveUserAuth() returned null')
|
||||
let finalRoute = '/api/guest/npub/enroll/admin'
|
||||
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' }
|
||||
},
|
||||
GetApp: async (): Promise<ResultError | ({ status: 'OK' }& Types.Application)> => {
|
||||
const auth = await params.retrieveAppAuth()
|
||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -5,10 +5,116 @@ export type ResultError = { status: 'ERROR', reason: string }
|
|||
|
||||
export type NostrClientParams = {
|
||||
pubDestination: string
|
||||
retrieveNostrAdminAuth: () => Promise<string | null>
|
||||
retrieveNostrMetricsAuth: () => Promise<string | null>
|
||||
retrieveNostrUserAuth: () => Promise<string | null>
|
||||
checkResult?: true
|
||||
}
|
||||
export default (params: NostrClientParams, send: (to:string, message: NostrRequest) => Promise<any>, subscribe: (to:string, message: NostrRequest, cb:(res:any)=> void) => void) => ({
|
||||
LndGetInfo: async (request: Types.LndGetInfoRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndGetInfoResponse)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'LndGetInfo',authIdentifier:auth, ...nostrRequest })
|
||||
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.LndGetInfoResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
AddApp: async (request: Types.AddAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AuthApp)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'AddApp',authIdentifier:auth, ...nostrRequest })
|
||||
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.AuthAppValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
AuthApp: async (request: Types.AuthAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AuthApp)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'AuthApp',authIdentifier:auth, ...nostrRequest })
|
||||
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.AuthAppValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
BanUser: async (request: Types.BanUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.BanUserResponse)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'BanUser',authIdentifier:auth, ...nostrRequest })
|
||||
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.BanUserResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUsageMetrics: async (): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => {
|
||||
const auth = await params.retrieveNostrMetricsAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUsageMetrics',authIdentifier:auth, ...nostrRequest })
|
||||
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.UsageMetricsValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetAppsMetrics: async (request: Types.AppsMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppsMetrics)> => {
|
||||
const auth = await params.retrieveNostrMetricsAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'GetAppsMetrics',authIdentifier:auth, ...nostrRequest })
|
||||
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.AppsMetricsValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLndMetrics: async (request: Types.LndMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndMetrics)> => {
|
||||
const auth = await params.retrieveNostrMetricsAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLndMetrics',authIdentifier:auth, ...nostrRequest })
|
||||
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.LndMetricsValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
LinkNPubThroughToken: async (request: Types.LinkNPubThroughTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
|
|
@ -21,6 +127,18 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
EnrollAdminToken: async (request: Types.EnrollAdminTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'EnrollAdminToken',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
UserHealth: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ export type NostrOptions = {
|
|||
logger?: Logger
|
||||
throwErrors?: true
|
||||
metricsCallback: (metrics: Types.RequestMetric[]) => void
|
||||
NostrAdminAuthGuard: (appId?:string, identifier?: string) => Promise<Types.AdminContext>
|
||||
NostrMetricsAuthGuard: (appId?:string, identifier?: string) => Promise<Types.MetricsContext>
|
||||
NostrUserAuthGuard: (appId?:string, identifier?: string) => Promise<Types.UserContext>
|
||||
}
|
||||
const logErrorAndReturnResponse = (error: Error, response: string, res: NostrResponse, logger: Logger, metric: Types.RequestMetric, metricsCallback: (metrics: Types.RequestMetric[]) => void) => {
|
||||
|
|
@ -29,6 +31,115 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
const stats: Types.RequestStats = { startMs, start: startTime, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
switch (req.rpcName) {
|
||||
case 'LndGetInfo':
|
||||
try {
|
||||
if (!methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
|
||||
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.LndGetInfoRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.LndGetInfo({rpcName:'LndGetInfo', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({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 }
|
||||
break
|
||||
case 'AddApp':
|
||||
try {
|
||||
if (!methods.AddApp) throw new Error('method: AddApp is not implemented')
|
||||
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.AddAppRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.AddApp({rpcName:'AddApp', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({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 }
|
||||
break
|
||||
case 'AuthApp':
|
||||
try {
|
||||
if (!methods.AuthApp) throw new Error('method: AuthApp is not implemented')
|
||||
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.AuthAppRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.AuthApp({rpcName:'AuthApp', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({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 }
|
||||
break
|
||||
case 'BanUser':
|
||||
try {
|
||||
if (!methods.BanUser) throw new Error('method: BanUser is not implemented')
|
||||
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.BanUserRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.BanUser({rpcName:'BanUser', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({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 }
|
||||
break
|
||||
case 'GetUsageMetrics':
|
||||
try {
|
||||
if (!methods.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented')
|
||||
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetUsageMetrics({rpcName:'GetUsageMetrics', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({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 }
|
||||
break
|
||||
case 'GetAppsMetrics':
|
||||
try {
|
||||
if (!methods.GetAppsMetrics) throw new Error('method: GetAppsMetrics is not implemented')
|
||||
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.AppsMetricsRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.GetAppsMetrics({rpcName:'GetAppsMetrics', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({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 }
|
||||
break
|
||||
case 'GetLndMetrics':
|
||||
try {
|
||||
if (!methods.GetLndMetrics) throw new Error('method: GetLndMetrics is not implemented')
|
||||
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.LndMetricsRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.GetLndMetrics({rpcName:'GetLndMetrics', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({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 }
|
||||
break
|
||||
case 'LinkNPubThroughToken':
|
||||
try {
|
||||
if (!methods.LinkNPubThroughToken) throw new Error('method: LinkNPubThroughToken is not implemented')
|
||||
|
|
@ -45,6 +156,22 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
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 }
|
||||
break
|
||||
case 'EnrollAdminToken':
|
||||
try {
|
||||
if (!methods.EnrollAdminToken) throw new Error('method: EnrollAdminToken is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.EnrollAdminTokenRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
await methods.EnrollAdminToken({rpcName:'EnrollAdminToken', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({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 }
|
||||
break
|
||||
case 'UserHealth':
|
||||
try {
|
||||
if (!methods.UserHealth) throw new Error('method: UserHealth is not implemented')
|
||||
|
|
@ -321,6 +448,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'EnrollAdminToken':
|
||||
if (!methods.EnrollAdminToken) {
|
||||
throw new Error('method not defined: EnrollAdminToken')
|
||||
} else {
|
||||
const error = Types.EnrollAdminTokenRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
await methods.EnrollAdminToken({...operation, ctx}); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'UserHealth':
|
||||
if (!methods.UserHealth) {
|
||||
throw new Error('method not defined: UserHealth')
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Binary file not shown.
BIN
proto/protoc-gen-pub_old
Executable file
BIN
proto/protoc-gen-pub_old
Executable file
Binary file not shown.
|
|
@ -92,42 +92,49 @@ service LightningPub {
|
|||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/lnd/getinfo";
|
||||
option (nostr) = true;
|
||||
};
|
||||
|
||||
rpc AddApp(structs.AddAppRequest) returns (structs.AuthApp) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/app/add";
|
||||
option (nostr) = true;
|
||||
};
|
||||
|
||||
rpc AuthApp(structs.AuthAppRequest) returns (structs.AuthApp) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/app/auth";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc BanUser(structs.BanUserRequest) returns (structs.BanUserResponse) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/user/ban";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetUsageMetrics(structs.Empty) returns (structs.UsageMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/usage";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetAppsMetrics(structs.AppsMetricsRequest) returns (structs.AppsMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/apps";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetLndMetrics(structs.LndMetricsRequest) returns (structs.LndMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/reports/lnd";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -182,10 +189,16 @@ service LightningPub {
|
|||
|
||||
rpc LinkNPubThroughToken(structs.LinkNPubThroughTokenRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "User";
|
||||
option(http_method) = "post";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/guest/npub/link";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc EnrollAdminToken(structs.EnrollAdminTokenRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/guest/npub/enroll/admin";
|
||||
option (nostr) = true;
|
||||
}
|
||||
//</Guest>
|
||||
|
||||
// <App>
|
||||
|
|
|
|||
|
|
@ -449,4 +449,8 @@ message LinkNPubThroughTokenRequest {
|
|||
message HttpCreds {
|
||||
string url = 1;
|
||||
string token = 2;
|
||||
}
|
||||
|
||||
message EnrollAdminTokenRequest {
|
||||
string admin_token = 1;
|
||||
}
|
||||
70
proto/wizard/wizard_methods.proto
Normal file
70
proto/wizard/wizard_methods.proto
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package wizard_methods;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
import "wizard_structs.proto";
|
||||
|
||||
option go_package = "github.com/shocknet/lightning.pub";
|
||||
option (file_options) = {
|
||||
supported_http_methods:["post", "get"];
|
||||
supported_auths:{
|
||||
id: "guest"
|
||||
name: "Guest"
|
||||
context:[]
|
||||
};
|
||||
};
|
||||
|
||||
message MethodQueryOptions {
|
||||
repeated string items = 1;
|
||||
}
|
||||
|
||||
extend google.protobuf.MethodOptions { // TODO: move this stuff to dep repo?
|
||||
string auth_type = 50003;
|
||||
string http_method = 50004;
|
||||
string http_route = 50005;
|
||||
MethodQueryOptions query = 50006;
|
||||
bool nostr = 50007;
|
||||
bool batch = 50008;
|
||||
|
||||
}
|
||||
|
||||
message ProtoFileOptions {
|
||||
message SupportedAuth {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
bool encrypted = 3;
|
||||
map<string,string> context = 4;
|
||||
}
|
||||
repeated SupportedAuth supported_auths = 1;
|
||||
repeated string supported_http_methods = 2;
|
||||
}
|
||||
|
||||
extend google.protobuf.FileOptions {
|
||||
ProtoFileOptions file_options = 50004;
|
||||
}
|
||||
|
||||
service Wizard {
|
||||
// <Guest>
|
||||
rpc WizardState(wizard_structs.Empty) returns (wizard_structs.StateResponse){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/wizard/state";
|
||||
};
|
||||
rpc WizardConfig(wizard_structs.ConfigRequest) returns (wizard_structs.Empty){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/wizard/config";
|
||||
};
|
||||
rpc GetAdminConnectInfo(wizard_structs.Empty) returns (wizard_structs.AdminConnectInfoResponse){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/wizard/admin_connect_info";
|
||||
};
|
||||
rpc GetServiceState(wizard_structs.Empty) returns (wizard_structs.ServiceStateResponse){
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/wizard/service_state";
|
||||
};
|
||||
// </Guest>
|
||||
}
|
||||
40
proto/wizard/wizard_structs.proto
Normal file
40
proto/wizard/wizard_structs.proto
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package wizard_structs;
|
||||
|
||||
option go_package = "github.com/shocknet/lightning.pub";
|
||||
|
||||
message Empty {}
|
||||
|
||||
message StateResponse {
|
||||
bool config_sent = 1;
|
||||
bool admin_linked = 2;
|
||||
}
|
||||
message ConfigRequest {
|
||||
string source_name = 1;
|
||||
string relay_url = 2;
|
||||
bool automate_liquidity = 3;
|
||||
bool push_backups_to_nostr = 4;
|
||||
}
|
||||
message AdminConnectInfoResponse {
|
||||
string nprofile = 1;
|
||||
oneof connect_info {
|
||||
string admin_token = 2;
|
||||
string enrolled_npub = 3;
|
||||
}
|
||||
}
|
||||
enum LndState {
|
||||
OFFLINE = 0;
|
||||
SYNCING = 1;
|
||||
ONLINE = 2;
|
||||
}
|
||||
message ServiceStateResponse {
|
||||
string provider_name = 1;
|
||||
repeated string relays = 2;
|
||||
string admin_npub = 3;
|
||||
bool relay_connected = 4;
|
||||
LndState lnd_state = 5;
|
||||
bool watchdog_ok = 6;
|
||||
string http_url = 7;
|
||||
string nprofile = 8;
|
||||
}
|
||||
91
proto/wizard_service/autogenerated/client.md
Normal file
91
proto/wizard_service/autogenerated/client.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# NOSTR API DEFINITION
|
||||
|
||||
|
||||
A nostr request will take the same parameter and give the same response as an http request, but it will use nostr as transport, to do that it will send encrypted events to the server public key, in the event 6 thing are required:
|
||||
- __rpcName__: string containing the name of the method
|
||||
- __params__: a map with the all the url params for the method
|
||||
- __query__: a map with the the url query for the method
|
||||
- __body__: the body of the method request
|
||||
- __requestId__: id of the request to be able to get a response
|
||||
|
||||
The nostr server will send back a message response, and inside the body there will also be a __requestId__ to identify the request this response is answering
|
||||
|
||||
## NOSTR Methods
|
||||
### These are the nostr methods the client implements to communicate with the API via nostr
|
||||
|
||||
# HTTP API DEFINITION
|
||||
|
||||
## Supported HTTP Auths
|
||||
### These are the supported http auth types, to give different type of access to the API users
|
||||
|
||||
- __Guest__:
|
||||
- expected context content
|
||||
|
||||
## 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__
|
||||
- http route: __/wizard/admin_connect_info__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [AdminConnectInfoResponse](#AdminConnectInfoResponse)
|
||||
|
||||
- GetServiceState
|
||||
- auth type: __Guest__
|
||||
- http method: __get__
|
||||
- http route: __/wizard/service_state__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [ServiceStateResponse](#ServiceStateResponse)
|
||||
|
||||
# INPUTS AND OUTPUTS
|
||||
|
||||
## Messages
|
||||
### The content of requests and response from the methods
|
||||
|
||||
### StateResponse
|
||||
- __config_sent__: _boolean_
|
||||
- __admin_linked__: _boolean_
|
||||
|
||||
### ConfigRequest
|
||||
- __source_name__: _string_
|
||||
- __relay_url__: _string_
|
||||
- __automate_liquidity__: _boolean_
|
||||
- __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_
|
||||
|
||||
### Empty
|
||||
## Enums
|
||||
### The enumerators used in the messages
|
||||
|
||||
### LndState
|
||||
- __OFFLINE__
|
||||
- __SYNCING__
|
||||
- __ONLINE__
|
||||
359
proto/wizard_service/autogenerated/debug.txt
Normal file
359
proto/wizard_service/autogenerated/debug.txt
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
([]*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) <nil>
|
||||
},
|
||||
query: ([]string) <nil>,
|
||||
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) <nil>
|
||||
},
|
||||
query: ([]string) <nil>,
|
||||
nostr: (bool) false,
|
||||
batch: (bool) false
|
||||
}),
|
||||
serverStream: (bool) false
|
||||
}),
|
||||
(*main.Method)(0xc00022a640)({
|
||||
in: (main.MethodMessage) {
|
||||
name: (string) (len=5) "Empty",
|
||||
hasZeroFields: (bool) true
|
||||
},
|
||||
name: (string) (len=19) "GetAdminConnectInfo",
|
||||
out: (main.MethodMessage) {
|
||||
name: (string) (len=24) "AdminConnectInfoResponse",
|
||||
hasZeroFields: (bool) false
|
||||
},
|
||||
opts: (*main.methodOptions)(0xc00009acc0)({
|
||||
authType: (*main.supportedAuth)(0xc0003c9c20)({
|
||||
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=26) "/wizard/admin_connect_info",
|
||||
params: ([]string) <nil>
|
||||
},
|
||||
query: ([]string) <nil>,
|
||||
nostr: (bool) false,
|
||||
batch: (bool) false
|
||||
}),
|
||||
serverStream: (bool) false
|
||||
}),
|
||||
(*main.Method)(0xc00022a690)({
|
||||
in: (main.MethodMessage) {
|
||||
name: (string) (len=5) "Empty",
|
||||
hasZeroFields: (bool) true
|
||||
},
|
||||
name: (string) (len=15) "GetServiceState",
|
||||
out: (main.MethodMessage) {
|
||||
name: (string) (len=20) "ServiceStateResponse",
|
||||
hasZeroFields: (bool) false
|
||||
},
|
||||
opts: (*main.methodOptions)(0xc00009ae40)({
|
||||
authType: (*main.supportedAuth)(0xc0003c9ce0)({
|
||||
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=21) "/wizard/service_state",
|
||||
params: ([]string) <nil>
|
||||
},
|
||||
query: ([]string) <nil>,
|
||||
nostr: (bool) false,
|
||||
batch: (bool) false
|
||||
}),
|
||||
serverStream: (bool) false
|
||||
})
|
||||
}
|
||||
|
||||
([]*main.Enum) (len=1 cap=1) {
|
||||
(*main.Enum)(0xc0003c9680)({
|
||||
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"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
(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",
|
||||
isMap: (bool) false,
|
||||
isArray: (bool) false,
|
||||
isEnum: (bool) false,
|
||||
isMessage: (bool) false,
|
||||
isOptional: (bool) false,
|
||||
oneOfName: (string) ""
|
||||
}),
|
||||
(string) (len=12) "admin_linked": (*main.Field)(0xc0003ee480)({
|
||||
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) ""
|
||||
})
|
||||
}
|
||||
}),
|
||||
(string) (len=13) "ConfigRequest": (*main.Message)(0xc0003c9560)({
|
||||
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)({
|
||||
name: (string) (len=18) "automate_liquidity",
|
||||
kind: (string) (len=4) "bool",
|
||||
isMap: (bool) false,
|
||||
isArray: (bool) false,
|
||||
isEnum: (bool) false,
|
||||
isMessage: (bool) false,
|
||||
isOptional: (bool) false,
|
||||
oneOfName: (string) ""
|
||||
}),
|
||||
(string) (len=21) "push_backups_to_nostr": (*main.Field)(0xc0003ee580)({
|
||||
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) ""
|
||||
}),
|
||||
(string) (len=11) "source_name": (*main.Field)(0xc0003ee4c0)({
|
||||
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) ""
|
||||
})
|
||||
}
|
||||
}),
|
||||
(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"
|
||||
})
|
||||
}
|
||||
}),
|
||||
(string) (len=20) "ServiceStateResponse": (*main.Message)(0xc0003c9620)({
|
||||
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)({
|
||||
name: (string) (len=10) "admin_npub",
|
||||
kind: (string) (len=6) "string",
|
||||
isMap: (bool) false,
|
||||
isArray: (bool) false,
|
||||
isEnum: (bool) false,
|
||||
isMessage: (bool) false,
|
||||
isOptional: (bool) false,
|
||||
oneOfName: (string) ""
|
||||
}),
|
||||
(string) (len=15) "relay_connected": (*main.Field)(0xc0003ee740)({
|
||||
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) ""
|
||||
}),
|
||||
(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",
|
||||
isMap: (bool) false,
|
||||
isArray: (bool) false,
|
||||
isEnum: (bool) false,
|
||||
isMessage: (bool) false,
|
||||
isOptional: (bool) false,
|
||||
oneOfName: (string) ""
|
||||
}),
|
||||
(string) (len=8) "http_url": (*main.Field)(0xc0003ee800)({
|
||||
name: (string) (len=8) "http_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=8) "nprofile": (*main.Field)(0xc0003ee840)({
|
||||
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=13) "provider_name": (*main.Field)(0xc0003ee680)({
|
||||
name: (string) (len=13) "provider_name",
|
||||
kind: (string) (len=6) "string",
|
||||
isMap: (bool) false,
|
||||
isArray: (bool) false,
|
||||
isEnum: (bool) false,
|
||||
isMessage: (bool) false,
|
||||
isOptional: (bool) false,
|
||||
oneOfName: (string) ""
|
||||
}),
|
||||
(string) (len=6) "relays": (*main.Field)(0xc0003ee6c0)({
|
||||
name: (string) (len=6) "relays",
|
||||
kind: (string) (len=6) "string",
|
||||
isMap: (bool) false,
|
||||
isArray: (bool) true,
|
||||
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"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
parsing file: wizard_structs 5
|
||||
parsing file: wizard_methods 2
|
||||
-> [{guest Guest map[]}]
|
||||
|
||||
([]interface {}) <nil>
|
||||
|
||||
120
proto/wizard_service/autogenerated/ts/express_server.ts
Normal file
120
proto/wizard_service/autogenerated/ts/express_server.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
|
||||
import express, { Response, json, urlencoded } from 'express'
|
||||
import cors from 'cors'
|
||||
import * as Types from './types.js'
|
||||
export type Logger = { log: (v: any) => void, error: (v: any) => void }
|
||||
export type ServerOptions = {
|
||||
allowCors?: true
|
||||
staticFiles?: string
|
||||
allowNotImplementedMethods?: true
|
||||
logger?: Logger
|
||||
throwErrors?: true
|
||||
logMethod?: true
|
||||
logBody?: true
|
||||
metricsCallback: (metrics: Types.RequestMetric[]) => void
|
||||
GuestAuthGuard: (authorizationHeader?: string) => Promise<Types.GuestContext>
|
||||
}
|
||||
declare module 'express-serve-static-core' { interface Request { startTime?: bigint, bodySize?: number, startTimeMs: number } }
|
||||
const logErrorAndReturnResponse = (error: Error, response: string, res: Response, logger: Logger, metric: Types.RequestMetric, metricsCallback: (metrics: Types.RequestMetric[]) => void) => {
|
||||
logger.error(error.message || error); metricsCallback([{ ...metric, error: response }]); res.json({ status: 'ERROR', reason: response })
|
||||
}
|
||||
export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
||||
const logger = opts.logger || { log: console.log, error: console.error }
|
||||
const app = express()
|
||||
if (opts.allowCors) {
|
||||
app.use(cors())
|
||||
}
|
||||
app.use((req, _, next) => { req.startTime = process.hrtime.bigint(); req.startTimeMs = Date.now(); next() })
|
||||
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}
|
||||
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.GetAdminConnectInfo) throw new Error('method: GetAdminConnectInfo 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.GetAdminConnectInfo({rpcName:'GetAdminConnectInfo', 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.GetServiceState) throw new Error('method: GetServiceState is not implemented')
|
||||
app.get('/wizard/service_state', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'GetServiceState', 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.GetServiceState) throw new Error('method: GetServiceState 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.GetServiceState({rpcName:'GetServiceState', 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 })})
|
||||
}
|
||||
var server: { close: () => void } | undefined
|
||||
return {
|
||||
Close: () => { if (!server) { throw new Error('tried closing server before starting') } else server.close() },
|
||||
Listen: (port: number) => { server = app.listen(port, () => logger.log('Wizard listening on port ' + port)) }
|
||||
}
|
||||
}
|
||||
68
proto/wizard_service/autogenerated/ts/http_client.ts
Normal file
68
proto/wizard_service/autogenerated/ts/http_client.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
import axios from 'axios'
|
||||
import * as Types from './types.js'
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
|
||||
export type ClientParams = {
|
||||
baseUrl: string
|
||||
retrieveGuestAuth: () => Promise<string | null>
|
||||
encryptCallback: (plain: any) => Promise<any>
|
||||
decryptCallback: (encrypted: any) => Promise<any>
|
||||
deviceId: string
|
||||
checkResult?: true
|
||||
}
|
||||
export default (params: ClientParams) => ({
|
||||
WizardState: async (): Promise<ResultError | ({ status: 'OK' }& Types.StateResponse)> => {
|
||||
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<ResultError | ({ status: 'OK' })> => {
|
||||
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<ResultError | ({ status: 'OK' }& Types.AdminConnectInfoResponse)> => {
|
||||
const auth = await params.retrieveGuestAuth()
|
||||
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
|
||||
let finalRoute = '/wizard/admin_connect_info'
|
||||
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.AdminConnectInfoResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetServiceState: async (): Promise<ResultError | ({ status: 'OK' }& Types.ServiceStateResponse)> => {
|
||||
const auth = await params.retrieveGuestAuth()
|
||||
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
|
||||
let finalRoute = '/wizard/service_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.ServiceStateResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
})
|
||||
11
proto/wizard_service/autogenerated/ts/nostr_client.ts
Normal file
11
proto/wizard_service/autogenerated/ts/nostr_client.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
import { NostrRequest } from './nostr_transport.js'
|
||||
import * as Types from './types.js'
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
|
||||
export type NostrClientParams = {
|
||||
pubDestination: string
|
||||
checkResult?: true
|
||||
}
|
||||
export default (params: NostrClientParams, send: (to:string, message: NostrRequest) => Promise<any>, subscribe: (to:string, message: NostrRequest, cb:(res:any)=> void) => void) => ({
|
||||
})
|
||||
34
proto/wizard_service/autogenerated/ts/nostr_transport.ts
Normal file
34
proto/wizard_service/autogenerated/ts/nostr_transport.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
|
||||
import * as Types from './types.js'
|
||||
export type Logger = { log: (v: any) => void, error: (v: any) => void }
|
||||
type NostrResponse = (message: object) => void
|
||||
export type NostrRequest = {
|
||||
rpcName?: string
|
||||
params?: Record<string, string>
|
||||
query?: Record<string, string>
|
||||
body?: any
|
||||
authIdentifier?: string
|
||||
requestId?: string
|
||||
appId?: string
|
||||
}
|
||||
export type NostrOptions = {
|
||||
logger?: Logger
|
||||
throwErrors?: true
|
||||
metricsCallback: (metrics: Types.RequestMetric[]) => void
|
||||
}
|
||||
const logErrorAndReturnResponse = (error: Error, response: string, res: NostrResponse, logger: Logger, metric: Types.RequestMetric, metricsCallback: (metrics: Types.RequestMetric[]) => void) => {
|
||||
logger.error(error.message || error); metricsCallback([{ ...metric, error: response }]); res({ status: 'ERROR', reason: response })
|
||||
}
|
||||
export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
||||
const logger = opts.logger || { log: console.log, error: console.error }
|
||||
return async (req: NostrRequest, res: NostrResponse, startString: string, startMs: number) => {
|
||||
const startTime = BigInt(startString)
|
||||
const info: Types.RequestInfo = { rpcName: req.rpcName || 'unkown', batch: false, nostr: true, batchSize: 0 }
|
||||
const stats: Types.RequestStats = { startMs, start: startTime, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
switch (req.rpcName) {
|
||||
default: logger.error('unknown rpc call name from nostr event:'+req.rpcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
214
proto/wizard_service/autogenerated/ts/types.ts
Normal file
214
proto/wizard_service/autogenerated/ts/types.ts
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
export type RequestInfo = { rpcName: string, batch: boolean, nostr: boolean, batchSize: number }
|
||||
export type RequestStats = { startMs:number, start:bigint, parse: bigint, guard: bigint, validate: bigint, handle: bigint }
|
||||
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 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 ServerMethods = {
|
||||
WizardState?: (req: WizardState_Input & {ctx: GuestContext }) => Promise<StateResponse>
|
||||
WizardConfig?: (req: WizardConfig_Input & {ctx: GuestContext }) => Promise<void>
|
||||
GetAdminConnectInfo?: (req: GetAdminConnectInfo_Input & {ctx: GuestContext }) => Promise<AdminConnectInfoResponse>
|
||||
GetServiceState?: (req: GetServiceState_Input & {ctx: GuestContext }) => Promise<ServiceStateResponse>
|
||||
}
|
||||
|
||||
export enum LndState {
|
||||
OFFLINE = 'OFFLINE',
|
||||
SYNCING = 'SYNCING',
|
||||
ONLINE = 'ONLINE',
|
||||
}
|
||||
export const enumCheckLndState = (e?: LndState): boolean => {
|
||||
for (const v in LndState) if (e === v) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export type OptionsBaseMessage = {
|
||||
allOptionalsAreSet?: true
|
||||
}
|
||||
|
||||
export type Empty = {
|
||||
}
|
||||
export const EmptyOptionalFields: [] = []
|
||||
export type EmptyOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
}
|
||||
export const EmptyValidate = (o?: Empty, opts: EmptyOptions = {}, path: string = 'Empty::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')
|
||||
|
||||
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 = {
|
||||
http_url: string
|
||||
nprofile: string
|
||||
provider_name: string
|
||||
relays: string[]
|
||||
admin_npub: string
|
||||
relay_connected: boolean
|
||||
lnd_state: LndState
|
||||
watchdog_ok: boolean
|
||||
}
|
||||
export const ServiceStateResponseOptionalFields: [] = []
|
||||
export type ServiceStateResponseOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
http_url_CustomCheck?: (v: string) => boolean
|
||||
nprofile_CustomCheck?: (v: string) => boolean
|
||||
provider_name_CustomCheck?: (v: string) => boolean
|
||||
relays_CustomCheck?: (v: string[]) => boolean
|
||||
admin_npub_CustomCheck?: (v: string) => boolean
|
||||
relay_connected_CustomCheck?: (v: boolean) => boolean
|
||||
lnd_state_CustomCheck?: (v: LndState) => 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 (!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.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`)
|
||||
|
||||
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`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export enum AdminConnectInfoResponse_connect_info_type {
|
||||
ADMIN_TOKEN = 'admin_token',
|
||||
ENROLLED_NPUB = 'enrolled_npub',
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import express from 'express';
|
||||
import path from 'path';
|
||||
|
||||
import { ServerOptions } from "../proto/autogenerated/ts/express_server";
|
||||
import { AdminContext, MetricsContext } from "../proto/autogenerated/ts/types";
|
||||
import Main from './services/main'
|
||||
|
|
@ -8,7 +8,6 @@ const serverOptions = (mainHandler: Main): ServerOptions => {
|
|||
const log = getLogger({})
|
||||
return {
|
||||
logger: { log, error: err => log(ERROR, err) },
|
||||
staticFiles: path.resolve('static'),
|
||||
AdminAuthGuard: adminAuth,
|
||||
MetricsAuthGuard: metricsAuth,
|
||||
AppAuthGuard: async (authHeader) => { return { app_id: mainHandler.applicationManager.DecodeAppToken(stripBearer(authHeader)) } },
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import nostrMiddleware from './nostrMiddleware.js'
|
|||
import { getLogger } from './services/helpers/logger.js';
|
||||
import { initMainHandler } from './services/main/init.js';
|
||||
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
||||
import { encodeNprofile } from './custom-nip19.js';
|
||||
|
||||
const start = async () => {
|
||||
const log = getLogger({})
|
||||
|
|
@ -16,7 +17,7 @@ const start = async () => {
|
|||
log("manual process ended")
|
||||
return
|
||||
}
|
||||
const { apps, mainHandler, liquidityProviderInfo } = keepOn
|
||||
const { apps, mainHandler, liquidityProviderInfo, wizard } = keepOn
|
||||
const serverMethods = GetServerMethods(mainHandler)
|
||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
||||
log("initializing nostr middleware")
|
||||
|
|
@ -27,6 +28,9 @@ const start = async () => {
|
|||
log("starting server")
|
||||
mainHandler.attachNostrSend(Send)
|
||||
mainHandler.StartBeacons()
|
||||
if (wizard) {
|
||||
wizard.AddConnectInfo(encodeNprofile({ pubkey: liquidityProviderInfo.publicKey, relays: nostrSettings.relays }), nostrSettings.relays)
|
||||
}
|
||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||
Server.Listen(mainSettings.servicePort)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,20 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
const nostrUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, pub || "")
|
||||
return { user_id: nostrUser.user.user_id, app_user_id: nostrUser.identifier, app_id: appId || "" }
|
||||
},
|
||||
NostrAdminAuthGuard: async (appId, pub) => {
|
||||
const adminNpub = mainHandler.adminManager.GetAdminNpub()
|
||||
if (!adminNpub) { throw new Error("admin access not configured") }
|
||||
if (pub !== adminNpub) { throw new Error("admin access denied") }
|
||||
log("admin access from", pub)
|
||||
return { admin_id: pub }
|
||||
},
|
||||
NostrMetricsAuthGuard: async (appId, pub) => {
|
||||
const adminNpub = mainHandler.adminManager.GetAdminNpub()
|
||||
if (!adminNpub) { throw new Error("admin access not configured") }
|
||||
if (pub !== adminNpub) { throw new Error("admin access denied") }
|
||||
log("operator access from", pub)
|
||||
return { operator_id: pub }
|
||||
},
|
||||
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
||||
logger: { log: console.log, error: err => log(ERROR, err) }
|
||||
})
|
||||
|
|
|
|||
97
src/services/main/adminManager.ts
Normal file
97
src/services/main/adminManager.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import fs, { watchFile } from "fs";
|
||||
import crypto from 'crypto'
|
||||
import { getLogger } from "../helpers/logger.js";
|
||||
import { MainSettings, getDataPath } from "./settings.js";
|
||||
import Storage from "../storage/index.js";
|
||||
export class AdminManager {
|
||||
storage: Storage
|
||||
log = getLogger({ component: "adminManager" })
|
||||
adminNpub = ""
|
||||
dataDir: string
|
||||
adminNpubPath: string
|
||||
adminEnrollTokenPath: string
|
||||
interval: NodeJS.Timer
|
||||
constructor(mainSettings: MainSettings, storage: Storage) {
|
||||
this.storage = storage
|
||||
this.dataDir = mainSettings.storageSettings.dataDir
|
||||
this.adminNpubPath = getDataPath(this.dataDir, 'admin.npub')
|
||||
this.adminEnrollTokenPath = getDataPath(this.dataDir, '.admin_enroll')
|
||||
this.start()
|
||||
}
|
||||
Stop = () => {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
|
||||
GenerateAdminEnrollToken = async () => {
|
||||
const token = crypto.randomBytes(32).toString('hex')
|
||||
fs.writeFileSync(this.adminEnrollTokenPath, token)
|
||||
return token
|
||||
}
|
||||
|
||||
start = () => {
|
||||
const adminNpub = this.ReadAdminNpub()
|
||||
if (adminNpub) {
|
||||
this.adminNpub = adminNpub
|
||||
} else if (!fs.existsSync(this.adminEnrollTokenPath)) {
|
||||
this.GenerateAdminEnrollToken()
|
||||
}
|
||||
this.interval = setInterval(() => {
|
||||
if (!this.adminNpub) {
|
||||
return
|
||||
}
|
||||
const deleted = !fs.existsSync(this.adminNpubPath)
|
||||
if (deleted) {
|
||||
this.adminNpub = ""
|
||||
this.log("admin npub file deleted")
|
||||
this.GenerateAdminEnrollToken()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ReadAdminEnrollToken = () => {
|
||||
try {
|
||||
return fs.readFileSync(this.adminEnrollTokenPath, 'utf8').trim()
|
||||
} catch (err: any) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
ReadAdminNpub = () => {
|
||||
try {
|
||||
return fs.readFileSync(this.adminNpubPath, 'utf8').trim()
|
||||
} catch (err: any) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
GetAdminNpub = () => {
|
||||
return this.adminNpub
|
||||
}
|
||||
|
||||
ClearExistingAdmin = () => {
|
||||
try {
|
||||
fs.unlinkSync(this.adminNpubPath)
|
||||
} catch (err: any) { }
|
||||
}
|
||||
|
||||
PromoteUserToAdmin = async (appId: string, appUserId: string, token: string) => {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
|
||||
const npub = appUser.nostr_public_key
|
||||
if (!npub) {
|
||||
throw new Error("no npub found for user")
|
||||
}
|
||||
let actualToken
|
||||
try {
|
||||
actualToken = fs.readFileSync(this.adminEnrollTokenPath, 'utf8').trim()
|
||||
} catch (err: any) {
|
||||
throw new Error("invalid enroll token")
|
||||
}
|
||||
if (token !== actualToken) {
|
||||
throw new Error("invalid enroll token")
|
||||
}
|
||||
fs.writeFileSync(this.adminNpubPath, npub)
|
||||
fs.unlinkSync(this.adminEnrollTokenPath)
|
||||
this.adminNpub = npub
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import { LiquidityProvider } from "./liquidityProvider.js"
|
|||
import { LiquidityManager } from "./liquidityManager.js"
|
||||
import { Utils } from "../helpers/utilsWrapper.js"
|
||||
import { RugPullTracker } from "./rugPullTracker.js"
|
||||
import { AdminManager } from "./adminManager.js"
|
||||
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
|
|
@ -33,21 +34,23 @@ export default class {
|
|||
lnd: LND
|
||||
settings: MainSettings
|
||||
userOperationsSub: UserOperationsSub | null = null
|
||||
adminManager: AdminManager
|
||||
productManager: ProductManager
|
||||
applicationManager: ApplicationManager
|
||||
appUserManager: AppUserManager
|
||||
paymentManager: PaymentManager
|
||||
paymentSubs: Record<string, ((op: Types.UserOperation) => void) | null> = {}
|
||||
metricsManager: MetricsManager
|
||||
liquidityProvider: LiquidityProvider
|
||||
liquidityManager: LiquidityManager
|
||||
liquidityProvider: LiquidityProvider
|
||||
utils: Utils
|
||||
rugPullTracker: RugPullTracker
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
constructor(settings: MainSettings, storage: Storage, utils: Utils) {
|
||||
constructor(settings: MainSettings, storage: Storage, adminManager: AdminManager, utils: Utils) {
|
||||
this.settings = settings
|
||||
this.storage = storage
|
||||
this.utils = utils
|
||||
this.adminManager = adminManager
|
||||
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b)
|
||||
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance)
|
||||
this.rugPullTracker = new RugPullTracker(this.storage, this.liquidityProvider)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import Storage from "../storage/index.js"
|
|||
import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js"
|
||||
import Main from "./index.js"
|
||||
import SanityChecker from "./sanityChecker.js"
|
||||
import { MainSettings } from "./settings.js"
|
||||
import { LoadMainSettingsFromEnv, MainSettings } from "./settings.js"
|
||||
import { Utils } from "../helpers/utilsWrapper.js"
|
||||
import { Wizard } from "../wizard/index.js"
|
||||
import { AdminManager } from "./adminManager.js"
|
||||
import { encodeNprofile } from "../../custom-nip19.js"
|
||||
export type AppData = {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
|
|
@ -22,18 +25,29 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
|
|||
}
|
||||
const unlocker = new Unlocker(mainSettings, storageManager)
|
||||
await unlocker.Unlock()
|
||||
const adminManager = new AdminManager(mainSettings, storageManager)
|
||||
let reloadedSettings = mainSettings
|
||||
let wizard: Wizard | null = null
|
||||
if (mainSettings.wizard) {
|
||||
wizard = new Wizard(mainSettings, storageManager, adminManager)
|
||||
const reload = await wizard.Configure()
|
||||
if (reload) {
|
||||
reloadedSettings = LoadMainSettingsFromEnv()
|
||||
}
|
||||
}
|
||||
|
||||
const mainHandler = new Main(mainSettings, storageManager, utils)
|
||||
const mainHandler = new Main(reloadedSettings, storageManager, adminManager, utils)
|
||||
await mainHandler.lnd.Warmup()
|
||||
if (!mainSettings.skipSanityCheck) {
|
||||
if (!reloadedSettings.skipSanityCheck) {
|
||||
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
||||
await sanityChecker.VerifyEventsLog()
|
||||
}
|
||||
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
|
||||
const existingWalletApp = await appsData.find(app => app.name === 'wallet' || app.name === 'wallet-test')
|
||||
const defaultNames = ['wallet', 'wallet-test', reloadedSettings.defaultAppName]
|
||||
const existingWalletApp = await appsData.find(app => defaultNames.includes(app.name))
|
||||
if (!existingWalletApp) {
|
||||
log("no default wallet app found, creating one...")
|
||||
const newWalletApp = await mainHandler.storage.applicationStorage.AddApplication('wallet', true)
|
||||
const newWalletApp = await mainHandler.storage.applicationStorage.AddApplication(reloadedSettings.defaultAppName, true)
|
||||
appsData.push(newWalletApp)
|
||||
}
|
||||
const apps: AppData[] = await Promise.all(appsData.map(app => {
|
||||
|
|
@ -44,7 +58,7 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
|
|||
return { privateKey: app.nostr_private_key, publicKey: app.nostr_public_key, appId: app.app_id, name: app.name }
|
||||
}
|
||||
}))
|
||||
const liquidityProviderApp = apps.find(app => app.name === 'wallet' || app.name === 'wallet-test')
|
||||
const liquidityProviderApp = apps.find(app => defaultNames.includes(app.name))
|
||||
if (!liquidityProviderApp) {
|
||||
throw new Error("wallet app not initialized correctly")
|
||||
}
|
||||
|
|
@ -59,7 +73,7 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
|
|||
return
|
||||
}
|
||||
mainHandler.paymentManager.watchDog.Start()
|
||||
return { mainHandler, apps, liquidityProviderInfo, liquidityProviderApp }
|
||||
return { mainHandler, apps, liquidityProviderInfo, liquidityProviderApp, wizard, adminManager }
|
||||
}
|
||||
|
||||
const processArgs = async (mainHandler: Main) => {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,11 @@ export class LiquidityManager {
|
|||
}
|
||||
|
||||
onNewBlock = async () => {
|
||||
await this.shouldDrainProvider()
|
||||
try {
|
||||
await this.shouldDrainProvider()
|
||||
} catch (err: any) {
|
||||
this.log("error in onNewBlock", err.message || err)
|
||||
}
|
||||
}
|
||||
|
||||
beforeInvoiceCreation = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ export class LiquidityProvider {
|
|||
this.client = newNostrClient({
|
||||
pubDestination: this.pubDestination,
|
||||
retrieveNostrUserAuth: async () => this.myPub,
|
||||
retrieveNostrAdminAuth: async () => this.myPub,
|
||||
retrieveNostrMetricsAuth: async () => this.myPub,
|
||||
}, this.clientSend, this.clientSub)
|
||||
|
||||
this.configuredInterval = setInterval(() => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { getLogger } from '../helpers/logger.js'
|
|||
import fs from 'fs'
|
||||
import crypto from 'crypto';
|
||||
import { LiquiditySettings, LoadLiquiditySettingsFromEnv } from './liquidityManager.js'
|
||||
|
||||
export type MainSettings = {
|
||||
storageSettings: StorageSettings,
|
||||
lndSettings: LndSettings,
|
||||
|
|
@ -29,6 +30,9 @@ export type MainSettings = {
|
|||
recordPerformance: boolean
|
||||
skipSanityCheck: boolean
|
||||
disableExternalPayments: boolean
|
||||
wizard: boolean
|
||||
defaultAppName: string
|
||||
pushBackupsToNostr: boolean
|
||||
}
|
||||
|
||||
export type BitcoinCoreSettings = {
|
||||
|
|
@ -41,6 +45,7 @@ export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettin
|
|||
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
||||
const storageSettings = LoadStorageSettingsFromEnv()
|
||||
const outgoingAppUserInvoiceFeeBps = EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0)
|
||||
|
||||
return {
|
||||
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
||||
lndSettings: LoadLndSettingsFromEnv(),
|
||||
|
|
@ -63,6 +68,9 @@ export const LoadMainSettingsFromEnv = (): MainSettings => {
|
|||
recordPerformance: process.env.RECORD_PERFORMANCE === 'true' || false,
|
||||
skipSanityCheck: process.env.SKIP_SANITY_CHECK === 'true' || false,
|
||||
disableExternalPayments: process.env.DISABLE_EXTERNAL_PAYMENTS === 'true' || false,
|
||||
wizard: process.env.WIZARD === 'true' || false,
|
||||
defaultAppName: process.env.DEFAULT_APP_NAME || "wallet",
|
||||
pushBackupsToNostr: process.env.PUSH_BACKUPS_TO_NOSTR === 'true' || false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ import { InitWalletReq } from '../lnd/initWalletReq.js';
|
|||
import Storage from '../storage/index.js'
|
||||
import { LightningClient } from '../../../proto/lnd/lightning.client.js';
|
||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||
type EncryptedData = { iv: string, encrypted: string }
|
||||
type Seed = { plaintextSeed: string[], encryptedSeed: EncryptedData }
|
||||
export class Unlocker {
|
||||
settings: MainSettings
|
||||
storage: Storage
|
||||
abortController = new AbortController()
|
||||
subbedToBackups = false
|
||||
log = getLogger({ component: "unlocker" })
|
||||
constructor(settings: MainSettings, storage: Storage) {
|
||||
this.settings = settings
|
||||
|
|
@ -23,7 +26,7 @@ export class Unlocker {
|
|||
this.abortController.abort()
|
||||
}
|
||||
|
||||
Unlock = async () => {
|
||||
getCreds = () => {
|
||||
const macroonPath = this.settings.lndSettings.mainNode.lndMacaroonPath
|
||||
const certPath = this.settings.lndSettings.mainNode.lndCertPath
|
||||
let macaroon = ""
|
||||
|
|
@ -40,16 +43,32 @@ export class Unlocker {
|
|||
throw err
|
||||
}
|
||||
}
|
||||
const { ln, pub } = macaroon === "" ? await this.InitFlow(lndCert) : await this.UnlockFlow(lndCert, macaroon)
|
||||
this.subscribeToBackups(ln, pub)
|
||||
return { lndCert, macaroon }
|
||||
}
|
||||
|
||||
UnlockFlow = async (lndCert: Buffer, macaroon: string) => {
|
||||
IsInitialized = () => {
|
||||
const { macaroon } = this.getCreds()
|
||||
return macaroon !== ''
|
||||
}
|
||||
|
||||
Unlock = async (): Promise<'created' | 'unlocked' | 'noaction'> => {
|
||||
const { lndCert, macaroon } = this.getCreds()
|
||||
if (macaroon === "") {
|
||||
const { ln, pub } = await this.InitFlow(lndCert)
|
||||
this.subscribeToBackups(ln, pub)
|
||||
return 'created'
|
||||
}
|
||||
const { ln, pub, action } = await this.UnlockFlow(lndCert, macaroon)
|
||||
this.subscribeToBackups(ln, pub)
|
||||
return action
|
||||
}
|
||||
|
||||
UnlockFlow = async (lndCert: Buffer, macaroon: string): Promise<{ ln: LightningClient, pub: string, action: 'unlocked' | 'noaction' }> => {
|
||||
const ln = this.GetLightningClient(lndCert, macaroon)
|
||||
const info = await this.GetLndInfo(ln)
|
||||
if (info.ok) {
|
||||
this.log("the wallet is already unlocked with pub:", info.pub)
|
||||
return { ln, pub: info.pub }
|
||||
return { ln, pub: info.pub, action: 'noaction' }
|
||||
}
|
||||
if (info.failure !== 'locked') {
|
||||
throw new Error("failed to get lnd info for reason: " + info.failure)
|
||||
|
|
@ -63,21 +82,30 @@ export class Unlocker {
|
|||
throw new Error("failed to unlock lnd wallet " + infoAfter.failure)
|
||||
}
|
||||
this.log("unlocked wallet with pub:", infoAfter.pub)
|
||||
return { ln, pub: infoAfter.pub }
|
||||
return { ln, pub: infoAfter.pub, action: 'unlocked' }
|
||||
}
|
||||
|
||||
InitFlow = async (lndCert: Buffer) => {
|
||||
this.log("macaroon not found, creating wallet...")
|
||||
const unlocker = this.GetUnlockerClient(lndCert)
|
||||
const { plaintextSeed, encryptedSeed } = await this.genSeed(unlocker)
|
||||
return this.initWallet(lndCert, unlocker, { plaintextSeed, encryptedSeed })
|
||||
}
|
||||
|
||||
genSeed = async (unlocker: WalletUnlockerClient): Promise<Seed> => {
|
||||
const entropy = crypto.randomBytes(16)
|
||||
const seedRes = await unlocker.genSeed({
|
||||
aezeedPassphrase: Buffer.alloc(0),
|
||||
seedEntropy: entropy
|
||||
}, DeadLineMetadata())
|
||||
this.log("seed created, encrypting and saving...")
|
||||
this.log("seed created")
|
||||
const { encryptedData } = this.EncryptWalletSeed(seedRes.response.cipherSeedMnemonic)
|
||||
return { plaintextSeed: seedRes.response.cipherSeedMnemonic, encryptedSeed: encryptedData }
|
||||
}
|
||||
|
||||
initWallet = async (lndCert: Buffer, unlocker: WalletUnlockerClient, seed: Seed) => {
|
||||
const walletPw = this.GetWalletPassword()
|
||||
const req = InitWalletReq(walletPw, seedRes.response.cipherSeedMnemonic)
|
||||
const req = InitWalletReq(walletPw, seed.plaintextSeed)
|
||||
const initRes = await unlocker.initWallet(req, DeadLineMetadata(60 * 1000))
|
||||
const adminMacaroon = Buffer.from(initRes.response.adminMacaroon).toString('hex')
|
||||
const ln = this.GetLightningClient(lndCert, adminMacaroon)
|
||||
|
|
@ -94,11 +122,13 @@ export class Unlocker {
|
|||
if (!info || !info.ok) {
|
||||
throw new Error("failed to init lnd wallet " + (info ? info.failure : "unknown error"))
|
||||
}
|
||||
await this.storage.liquidityStorage.SaveNodeSeed(info.pub, JSON.stringify(encryptedData))
|
||||
await this.storage.liquidityStorage.SaveNodeSeed(info.pub, JSON.stringify(seed.encryptedSeed))
|
||||
this.log("created wallet with pub:", info.pub)
|
||||
return { ln, pub: info.pub }
|
||||
}
|
||||
|
||||
|
||||
|
||||
GetLndInfo = async (ln: LightningClient): Promise<{ ok: false, failure: 'locked' | 'unknown' } | { ok: true, pub: string }> => {
|
||||
while (true) {
|
||||
try {
|
||||
|
|
@ -198,6 +228,10 @@ export class Unlocker {
|
|||
}
|
||||
|
||||
subscribeToBackups = async (ln: LightningClient, pub: string) => {
|
||||
if (this.subbedToBackups) {
|
||||
return
|
||||
}
|
||||
this.subbedToBackups = true
|
||||
this.log("subscribing to channel backups for: ", pub)
|
||||
const stream = ln.subscribeChannelBackups({}, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(async (msg) => {
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ export default class Handler {
|
|||
await p
|
||||
sent = true
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
log(e)
|
||||
}
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -214,6 +214,13 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.LinkNpubThroughToken(ctx, req)
|
||||
}
|
||||
},
|
||||
EnrollAdminToken: async ({ ctx, req }) => {
|
||||
const err = Types.EnrollAdminTokenRequestValidate(req, {
|
||||
admin_token_CustomCheck: token => token !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.adminManager.PromoteUserToAdmin(ctx.app_id, ctx.app_user_id, req.admin_token)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { DataSource, EntityManager } from "typeorm"
|
||||
import fs from 'fs'
|
||||
import NewDB, { DbSettings, LoadDbSettingsFromEnv } from "./db.js"
|
||||
import ProductStorage from './productStorage.js'
|
||||
import ApplicationStorage from './applicationStorage.js'
|
||||
|
|
@ -43,6 +44,7 @@ export default class {
|
|||
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage, this.txQueue)
|
||||
this.metricsStorage = new MetricsStorage(this.settings)
|
||||
this.liquidityStorage = new LiquidityStorage(this.DB, this.txQueue)
|
||||
try { if (this.settings.dataDir) fs.mkdirSync(this.settings.dataDir) } catch (e) { }
|
||||
const executedMetricsMigrations = await this.metricsStorage.Connect(metricsMigrations)
|
||||
return { executedMigrations, executedMetricsMigrations };
|
||||
}
|
||||
|
|
|
|||
171
src/services/wizard/index.ts
Normal file
171
src/services/wizard/index.ts
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path';
|
||||
import { config as loadEnvFile } from 'dotenv'
|
||||
import { getLogger } from "../helpers/logger.js"
|
||||
import NewWizardServer from "../../../proto/wizard_service/autogenerated/ts/express_server.js"
|
||||
import * as WizardTypes from "../../../proto/wizard_service/autogenerated/ts/types.js"
|
||||
import { MainSettings } from "../main/settings.js"
|
||||
import Storage from '../storage/index.js'
|
||||
import { Unlocker } from "../main/unlocker.js"
|
||||
import { AdminManager } from '../main/adminManager.js';
|
||||
export type WizardSettings = {
|
||||
sourceName: string
|
||||
relayUrl: string
|
||||
automateLiquidity: boolean
|
||||
pushBackupsToNostr: boolean
|
||||
}
|
||||
const defaultProviderPub = ""
|
||||
export class Wizard {
|
||||
log = getLogger({ component: "wizard" })
|
||||
settings: MainSettings
|
||||
adminManager: AdminManager
|
||||
storage: Storage
|
||||
configQueue: { res: (reload: boolean) => void }[] = []
|
||||
pendingConfig: WizardSettings | null = null
|
||||
awaitingNprofile: { res: (nprofile: string) => void }[] = []
|
||||
nprofile = ""
|
||||
relays: string[] = []
|
||||
constructor(mainSettings: MainSettings, storage: Storage, adminManager: AdminManager) {
|
||||
this.settings = mainSettings
|
||||
this.adminManager = adminManager
|
||||
this.storage = storage
|
||||
this.log('Starting wizard...')
|
||||
const wizardServer = NewWizardServer({
|
||||
WizardState: async () => { return this.WizardState() },
|
||||
WizardConfig: async ({ req }) => { return this.wizardConfig(req) },
|
||||
GetAdminConnectInfo: async () => { return this.GetAdminConnectInfo() },
|
||||
GetServiceState: async () => { return this.GetServiceState() }
|
||||
}, { GuestAuthGuard: async () => "", metricsCallback: () => { }, staticFiles: 'static' })
|
||||
wizardServer.Listen(mainSettings.servicePort + 1)
|
||||
}
|
||||
|
||||
GetServiceState = async (): Promise<WizardTypes.ServiceStateResponse> => {
|
||||
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
|
||||
}
|
||||
}
|
||||
WizardState = async (): Promise<WizardTypes.StateResponse> => {
|
||||
return {
|
||||
config_sent: this.pendingConfig !== null,
|
||||
admin_linked: this.adminManager.GetAdminNpub() !== "",
|
||||
}
|
||||
}
|
||||
IsInitialized = () => {
|
||||
return !!this.adminManager.GetAdminNpub()
|
||||
}
|
||||
|
||||
GetAdminConnectInfo = async (): Promise<WizardTypes.AdminConnectInfoResponse> => {
|
||||
const nprofile = await this.getNprofile()
|
||||
const enrolledAdmin = this.adminManager.GetAdminNpub()
|
||||
if (enrolledAdmin !== "") {
|
||||
return {
|
||||
nprofile, connect_info: {
|
||||
type: WizardTypes.AdminConnectInfoResponse_connect_info_type.ENROLLED_NPUB,
|
||||
enrolled_npub: enrolledAdmin
|
||||
}
|
||||
}
|
||||
}
|
||||
const adminEnroll = this.adminManager.ReadAdminEnrollToken()
|
||||
if (adminEnroll !== "") {
|
||||
return {
|
||||
nprofile, connect_info: {
|
||||
type: WizardTypes.AdminConnectInfoResponse_connect_info_type.ADMIN_TOKEN,
|
||||
admin_token: adminEnroll
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error("something went wrong initializing admin creds")
|
||||
}
|
||||
|
||||
getNprofile = async (): Promise<string> => {
|
||||
if (this.nprofile !== "") {
|
||||
return this.nprofile
|
||||
}
|
||||
console.log("waiting for nprofile")
|
||||
return new Promise((res) => {
|
||||
this.awaitingNprofile.push({ res })
|
||||
})
|
||||
}
|
||||
|
||||
AddConnectInfo = (nprofile: string, relays: string[]) => {
|
||||
this.nprofile = nprofile
|
||||
this.awaitingNprofile.forEach(q => q.res(nprofile))
|
||||
this.awaitingNprofile = []
|
||||
}
|
||||
|
||||
Configure = async (): Promise<boolean> => {
|
||||
if (this.IsInitialized() || this.pendingConfig !== null) {
|
||||
return false
|
||||
}
|
||||
return new Promise((res) => {
|
||||
this.configQueue.push({ res })
|
||||
})
|
||||
}
|
||||
|
||||
wizardConfig = async (req: WizardTypes.ConfigRequest): Promise<void> => {
|
||||
const err = WizardTypes.ConfigRequestValidate(req, {
|
||||
source_name_CustomCheck: source => source !== '',
|
||||
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 }
|
||||
this.updateEnvFile(pendingConfig)
|
||||
this.configQueue.forEach(q => q.res(true))
|
||||
this.configQueue = []
|
||||
return
|
||||
}
|
||||
|
||||
updateEnvFile = (pendingConfig: WizardSettings) => {
|
||||
let envFileContent: string[] = []
|
||||
try {
|
||||
envFileContent = fs.readFileSync('.env', 'utf-8').split('\n')
|
||||
} catch (err: any) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const toMerge: string[] = []
|
||||
const sourceNameIndex = envFileContent.findIndex(line => line.startsWith('DEFAULT_APP_NAME'))
|
||||
if (sourceNameIndex === -1) {
|
||||
toMerge.push(`DEFAULT_APP_NAME=${pendingConfig.sourceName}`)
|
||||
} else {
|
||||
envFileContent[sourceNameIndex] = `DEFAULT_APP_NAME=${pendingConfig.sourceName}`
|
||||
}
|
||||
const relayUrlIndex = envFileContent.findIndex(line => line.startsWith('RELAY_URL'))
|
||||
if (relayUrlIndex === -1) {
|
||||
toMerge.push(`RELAY_URL=${pendingConfig.relayUrl}`)
|
||||
} else {
|
||||
envFileContent[relayUrlIndex] = `RELAY_URL=${pendingConfig.relayUrl}`
|
||||
}
|
||||
|
||||
const automateLiquidityIndex = envFileContent.findIndex(line => line.startsWith('LIQUIDITY_PROVIDER_PUB'))
|
||||
if (automateLiquidityIndex === -1) {
|
||||
toMerge.push(`LIQUIDITY_PROVIDER_PUB=${pendingConfig.automateLiquidity ? defaultProviderPub : ""}`)
|
||||
} else {
|
||||
envFileContent[automateLiquidityIndex] = `LIQUIDITY_PROVIDER_PUB=`
|
||||
}
|
||||
|
||||
const pushBackupsToNostrIndex = envFileContent.findIndex(line => line.startsWith('PUSH_BACKUPS_TO_NOSTR'))
|
||||
if (pushBackupsToNostrIndex === -1) {
|
||||
toMerge.push(`PUSH_BACKUPS_TO_NOSTR=${pendingConfig.pushBackupsToNostr ? 'true' : 'false'}`)
|
||||
} else {
|
||||
envFileContent[pushBackupsToNostrIndex] = `PUSH_BACKUPS_TO_NOSTR=${pendingConfig.pushBackupsToNostr ? 'true' : 'false'}`
|
||||
}
|
||||
const merged = [...envFileContent, ...toMerge].join('\n')
|
||||
fs.writeFileSync('.env', merged)
|
||||
loadEnvFile()
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import LND from '../services/lnd/lnd.js'
|
|||
import { getLogger, resetDisabledLoggers } from '../services/helpers/logger.js'
|
||||
import { LiquidityProvider } from '../services/main/liquidityProvider.js'
|
||||
import { Utils } from '../services/helpers/utilsWrapper.js'
|
||||
import { AdminManager } from '../services/main/adminManager.js'
|
||||
chai.use(chaiString)
|
||||
export const expect = chai.expect
|
||||
export type Describe = (message: string, failure?: boolean) => void
|
||||
|
|
@ -30,6 +31,7 @@ export type TestBase = {
|
|||
externalAccessToMainLnd: LND
|
||||
externalAccessToOtherLnd: LND
|
||||
externalAccessToThirdLnd: LND
|
||||
adminManager: AdminManager
|
||||
d: Describe
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +65,8 @@ export const SetupTest = async (d: Describe): Promise<TestBase> => {
|
|||
expect, main, app,
|
||||
user1, user2,
|
||||
externalAccessToMainLnd, externalAccessToOtherLnd, externalAccessToThirdLnd,
|
||||
d
|
||||
d,
|
||||
adminManager: initialized.adminManager
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +75,7 @@ export const teardown = async (T: TestBase) => {
|
|||
T.externalAccessToMainLnd.Stop()
|
||||
T.externalAccessToOtherLnd.Stop()
|
||||
T.externalAccessToThirdLnd.Stop()
|
||||
T.adminManager.Stop()
|
||||
resetDisabledLoggers()
|
||||
console.log("teardown")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,102 +1,142 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat"
|
||||
/>
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/backup.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img
|
||||
src="img/pub_logo.png"
|
||||
width="38px"
|
||||
height="auto"
|
||||
alt="Lightning Pub logo"
|
||||
/>
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<button class="icon-button back-button" onclick="history.back()">
|
||||
<img src="img/back.svg" alt="" />
|
||||
</button>
|
||||
<h2>Choose a Recovery Method</h2>
|
||||
<p class="header-title">
|
||||
<span style="font-weight: bold">New Node! 🎉</span> It's important
|
||||
to backup your keys.
|
||||
</p>
|
||||
</section>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/backup.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
|
||||
<div class="line"></div>
|
||||
<body>
|
||||
<header>
|
||||
<img src="img/pub_logo.png" width="38px" height="auto" alt="Lightning Pub logo" />
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<section class="setup-content">
|
||||
<div class="description-box">
|
||||
<div class="description">
|
||||
In addition to your seed phrase, you also need channel details to recover funds should your node experience a hardware failure.
|
||||
</div>
|
||||
<br />
|
||||
<div class="description">
|
||||
It's important always to have the latest version of this file. Fortunately, it's small enough to automatically store on the Nostr relay.
|
||||
</div>
|
||||
</div>
|
||||
<div class="warning-text">
|
||||
If you did not choose the developers relay, be sure your relay has
|
||||
adequate storage policies to hold NIP78 events.
|
||||
</div>
|
||||
<div class="checkbox-container">
|
||||
<div class="checkbox" style="margin-top: 12px">
|
||||
<input type="checkbox" id="backup" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="backup">
|
||||
Encrypted Backup to Nostr Relay
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox-container">
|
||||
<div class="checkbox manual-checkbox" style="margin-top: 12px">
|
||||
<input type="checkbox" id="manual-backup" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="manual-backup" >
|
||||
DO NOT store on relay (Manual Backups)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="push-button hidden-button"
|
||||
onclick="location.href='seed.html'"
|
||||
style="margin-top: 60px;"
|
||||
id="next-button"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<button class="icon-button back-button" onclick="history.back()">
|
||||
<img src="img/back.svg" alt="" />
|
||||
</button>
|
||||
<h2>Choose a Recovery Method</h2>
|
||||
<p class="header-title">
|
||||
<span style="font-weight: bold">New Node! 🎉</span> It's important
|
||||
to backup your keys.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
<div class="line"></div>
|
||||
|
||||
<section class="setup-content">
|
||||
<div class="description-box">
|
||||
<div class="description">
|
||||
In addition to your seed phrase, you also need channel details to recover funds should your node experience a
|
||||
hardware failure.
|
||||
</div>
|
||||
<br />
|
||||
<div class="description">
|
||||
It's important always to have the latest version of this file. Fortunately, it's small enough to automatically
|
||||
store on the Nostr relay.
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
<div class="warning-text">
|
||||
If you did not choose the developers relay, be sure your relay has
|
||||
adequate storage policies to hold NIP78 events.
|
||||
</div>
|
||||
<div class="checkbox-container">
|
||||
<div class="checkbox" style="margin-top: 12px">
|
||||
<input type="checkbox" id="backup" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="backup">
|
||||
Encrypted Backup to Nostr Relay
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox-container">
|
||||
<div class="checkbox manual-checkbox" style="margin-top: 12px">
|
||||
<input type="checkbox" id="manual-backup" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="manual-backup">
|
||||
DO NOT store on relay (Manual Backups)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p id="errorText" style="color:red"></p>
|
||||
</div>
|
||||
<button class="push-button hidden-button" style="margin-top: 60px;" id="next-button">
|
||||
Next
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="js/backup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
|
||||
<script src="js/backup.js"></script>
|
||||
<script>
|
||||
const sendConfig = async () => {
|
||||
const req = {
|
||||
source_name: localStorage.getItem("wizard/nodeName"),
|
||||
relay_url: localStorage.getItem("wizard/relayUrl"),
|
||||
automate_liquidity: localStorage.getItem("wizard/liquidity") === 'automate',
|
||||
push_backups_to_nostr: localStorage.getItem("wizard/backup") === 'backup',
|
||||
}
|
||||
const res = await fetch("/wizard/config", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(req)
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
document.getElementById('errorText').innerText = "failed to start service"
|
||||
return
|
||||
}
|
||||
const j = await res.json()
|
||||
if (j.status !== 'OK') {
|
||||
document.getElementById('errorText').innerText = "failed to start service" + j.reason
|
||||
return
|
||||
}
|
||||
location.href = 'connect.html'
|
||||
}
|
||||
document.getElementById("next-button").onclick = (e) => {
|
||||
const backup = document.getElementById('backup').checked
|
||||
const manual = document.getElementById('manual-backup').checked
|
||||
if (!backup && !manual) {
|
||||
document.getElementById('errorText').innerText = 'Please select an option'
|
||||
return
|
||||
}
|
||||
if (backup && manual) {
|
||||
document.getElementById('errorText').innerText = 'Please select only one option'
|
||||
return
|
||||
}
|
||||
if (backup) {
|
||||
localStorage.setItem('wizard/backup', 'backup')
|
||||
} else {
|
||||
localStorage.setItem('wizard/backup', 'manual')
|
||||
}
|
||||
sendConfig()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,80 +1,116 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat"
|
||||
/>
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/connect.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img
|
||||
src="img/pub_logo.png"
|
||||
width="38px"
|
||||
height="auto"
|
||||
alt="Lightning Pub logo"
|
||||
/>
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<button class="icon-button back-button" onclick="history.back()">
|
||||
<img src="img/back.svg" alt="" />
|
||||
</button>
|
||||
<h2>Connect</h2>
|
||||
<p class="header-title">
|
||||
You can now manage your node remotely
|
||||
</p>
|
||||
</section>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/connect.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
|
||||
<div class="line"></div>
|
||||
<body>
|
||||
<header>
|
||||
<img src="img/pub_logo.png" width="38px" height="auto" alt="Lightning Pub logo" />
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<section class="setup-content">
|
||||
<div>For dashboard access, use <a href="https://preview.uxpin.com/ae6e6372ab26cd13438d486d4d1ac9d184ec8e82#/pages/164889267" style="color: #2aabe9;" target="_blank">ShockWallet</a> and tap the logo 3 times.</div>
|
||||
<div style="font-size: 13px; margin-top: 5px;">Scan the QR or Copy-Paste the string to establish the connection.</div>
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<div class="qrcode-box" id="codebox">
|
||||
<div style="font-size: 11px;">
|
||||
<div style="text-align: center; color: #a3a3a3;">Code contains a one-time pairing secret</div>
|
||||
<div style="text-align: center; color: #c434e0;" id="click-text">Click to reveal</div>
|
||||
</div>
|
||||
<div id="qrcode"></div>
|
||||
<div style="color: #a3a3a3; font-size: 11px;">
|
||||
<div>npub123abcdefghhhhhhhhhhhhhhh</div>
|
||||
<div>relay.lightning.pub</div>
|
||||
</div>
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<button class="icon-button back-button" onclick="history.back()">
|
||||
<img src="img/back.svg" alt="" />
|
||||
</button>
|
||||
<h2>Connect</h2>
|
||||
<p class="header-title">
|
||||
You can now manage your node remotely
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<section class="setup-content">
|
||||
<div>For dashboard access, use <a
|
||||
href="https://preview.uxpin.com/ae6e6372ab26cd13438d486d4d1ac9d184ec8e82#/pages/164889267"
|
||||
style="color: #2aabe9;" target="_blank">ShockWallet</a> and tap the logo 3 times.</div>
|
||||
<div style="font-size: 13px; margin-top: 5px;">Scan the QR or Copy-Paste the string to establish the connection.
|
||||
</div>
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<div class="qrcode-box" id="codebox">
|
||||
<div style="font-size: 11px;">
|
||||
<div style="text-align: center; color: #a3a3a3;">Code contains a one-time pairing secret</div>
|
||||
<div style="text-align: center; color: #c434e0;" id="click-text">Click to reveal</div>
|
||||
</div>
|
||||
<div id="qrcode"></div>
|
||||
<div style="color: #a3a3a3; font-size: 11px;">
|
||||
<div id="connectString"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
</section>
|
||||
</main>
|
||||
<p class="errorText" style="color:red"></p>
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js"></script>
|
||||
<script src="https://cdn.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js"></script>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
<script src="js/connect.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<script src="js/connect.js"></script>
|
||||
<script>
|
||||
const fetchInfo = async () => {
|
||||
console.log("fewtching...")
|
||||
const res = await fetch("/wizard/admin_connect_info")
|
||||
console.log(res)
|
||||
if (res.status !== 200) {
|
||||
document.getElementById('errorText').innerText = "failed to get connection info"
|
||||
return
|
||||
}
|
||||
const j = await res.json()
|
||||
console.log(j)
|
||||
if (j.status !== 'OK') {
|
||||
document.getElementById('errorText').innerText = "failed to get connection info" + j.reason
|
||||
return
|
||||
}
|
||||
if (j.connect_info.enrolled_npub) {
|
||||
location.href = 'status.html'
|
||||
} else {
|
||||
const connectString = j.nprofile + ":" + j.connect_info.admin_token
|
||||
console.log({ connectString })
|
||||
const qrElement = document.getElementById("qrcode")
|
||||
qrElement.onclick = () => {
|
||||
document.navigator.clipboard.writeText(connectString)
|
||||
}
|
||||
const qrcode = new QRCode(qrElement, {
|
||||
text: connectString,
|
||||
colorDark: "#000000",
|
||||
colorLight: "#ffffff",
|
||||
width: 157,
|
||||
height: 157,
|
||||
// correctLevel : QRCode.CorrectLevel.H
|
||||
});
|
||||
document.getElementById('connectString').innerHTML = connectString
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
||||
fetchInfo()
|
||||
} catch (e) { console.log({ e }) }
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,89 +1,112 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat"
|
||||
/>
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img
|
||||
src="img/pub_logo.png"
|
||||
width="38px"
|
||||
height="auto"
|
||||
alt="Lightning Pub logo"
|
||||
/>
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<h2>Setup your Pub</h2>
|
||||
<p class="header-title">
|
||||
</p>
|
||||
</section>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
</head>
|
||||
|
||||
<div class="line"></div>
|
||||
<body>
|
||||
<header>
|
||||
<img src="img/pub_logo.png" width="38px" height="auto" alt="Lightning Pub logo" />
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<section class="setup-content">
|
||||
<div class="input-group">
|
||||
<span>Give this node a name that wallet users will see:</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nodey McNodeFace"
|
||||
value=""
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<h2>Setup your Pub</h2>
|
||||
<p class="header-title">
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="input-group" style="margin-top: 38px">
|
||||
<span>If you want to use a specific Nostr relay, enter it now:</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="wss://relay.lightning.pub"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="checkbox" style="margin-top: 12px">
|
||||
<input type="checkbox" id="customCheckbox" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="customCheckbox">
|
||||
Use the default managed relay service and auto-pay 1000 sats
|
||||
per month to support developers
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="push-button"
|
||||
onclick="location.href='liquidity.html'"
|
||||
style="margin-top: 60px"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
</div>
|
||||
<section class="setup-content">
|
||||
<div class="input-group">
|
||||
<span>Give this node a name that wallet users will see:</span>
|
||||
<input type="text" placeholder="Nodey McNodeFace" value="" style="width: 100%" id="nodeName" />
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<div class="input-group" style="margin-top: 38px">
|
||||
<span>If you want to use a specific Nostr relay, enter it now:</span>
|
||||
<input type="text" placeholder="wss://relay.lightning.pub" style="width: 100%" id="relayUrl" />
|
||||
</div>
|
||||
|
||||
<div class="checkbox" style="margin-top: 12px">
|
||||
<input type="checkbox" id="customCheckbox" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="customCheckbox">
|
||||
Use the default managed relay service and auto-pay 1000 sats
|
||||
per month to support developers
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p id="errorText" style="color:red"></p>
|
||||
</div>
|
||||
|
||||
<button class="push-button" style="margin-top: 60px" id="liquidityBtn">
|
||||
Next
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
<script>
|
||||
|
||||
document.getElementById("liquidityBtn").onclick = (e) => {
|
||||
const nodeName = document.getElementById("nodeName").value;
|
||||
const relayUrl = document.getElementById("relayUrl").value;
|
||||
const checked = document.getElementById("customCheckbox").checked;
|
||||
if (!nodeName) {
|
||||
document.getElementById("errorText").innerText = "Please enter a node name";
|
||||
return;
|
||||
}
|
||||
if (!checked && !relayUrl) {
|
||||
document.getElementById("errorText").innerText = "Please enter a relay URL or check the default relay box";
|
||||
return;
|
||||
}
|
||||
localStorage.setItem("wizard/nodeName", nodeName);
|
||||
if (checked) {
|
||||
localStorage.setItem("wizard/relayUrl", "wss://relay.lightning.pub");
|
||||
} else {
|
||||
localStorage.setItem("wizard/relayUrl", relayUrl);
|
||||
}
|
||||
location.href = 'liquidity.html'
|
||||
}
|
||||
|
||||
fetch("/wizard/state").then((res) => {
|
||||
if (res.status === 200) {
|
||||
res.json().then((data) => {
|
||||
if (data.admin_linked) {
|
||||
location.href = 'status.html'
|
||||
} else if (data.config_sent) {
|
||||
location.href = 'connect.html'
|
||||
} else {
|
||||
console.log("ready to initialize")
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,93 +1,108 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat"
|
||||
/>
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/liquidity.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img
|
||||
src="img/pub_logo.png"
|
||||
width="38px"
|
||||
height="auto"
|
||||
alt="Lightning Pub logo"
|
||||
/>
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<button class="icon-button back-button" onclick="history.back()">
|
||||
<img src="img/back.svg" alt="" />
|
||||
</button>
|
||||
<h2>Manage Node Liquidity</h2>
|
||||
<p class="header-title">
|
||||
How do you want to manage Lightning channels?
|
||||
</p>
|
||||
</section>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/liquidity.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
|
||||
<div class="line"></div>
|
||||
<body>
|
||||
<header>
|
||||
<img src="img/pub_logo.png" width="38px" height="auto" alt="Lightning Pub logo" />
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<section class="setup-content">
|
||||
<div class="checkbox" style="margin-top: 60px">
|
||||
<input type="checkbox" id="automate" data-group="service" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="automate" class="automate">
|
||||
Use Automation Service
|
||||
<div class="question-box">
|
||||
<button class="icon-button" id="show-question">
|
||||
<img src="img/question.svg" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="question-content" id="question-content">
|
||||
Automation helps reduce the fees you pay by trusting peers temporarily until your node balance is sufficient to open a balanced Lightning channel.
|
||||
<button class="icon-button close-button" id="close-question">
|
||||
<img src="img/close.svg" alt="" />
|
||||
</button>
|
||||
<a href="https://docs.shock.network/" target="_blank" class="marked question-more">Learn More</a>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox" style="margin-top: 30px">
|
||||
<input type="checkbox" id="manual" data-group="service" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="manual">Manage my channels manually</label>
|
||||
</div>
|
||||
<button
|
||||
class="push-button"
|
||||
onclick="location.href='backup.html'"
|
||||
style="margin-top: 60px"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<button class="icon-button back-button" onclick="history.back()">
|
||||
<img src="img/back.svg" alt="" />
|
||||
</button>
|
||||
<h2>Manage Node Liquidity</h2>
|
||||
<p class="header-title">
|
||||
How do you want to manage Lightning channels?
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<section class="setup-content">
|
||||
<div class="checkbox" style="margin-top: 60px">
|
||||
<input type="checkbox" id="automate" data-group="service" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="automate" class="automate">
|
||||
Use Automation Service
|
||||
<div class="question-box">
|
||||
<button class="icon-button" id="show-question">
|
||||
<img src="img/question.svg" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="question-content" id="question-content">
|
||||
Automation helps reduce the fees you pay by trusting peers temporarily until your node balance is sufficient
|
||||
to open a balanced Lightning channel.
|
||||
<button class="icon-button close-button" id="close-question">
|
||||
<img src="img/close.svg" alt="" />
|
||||
</button>
|
||||
<a href="https://docs.shock.network/" target="_blank" class="marked question-more">Learn More</a>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
<div class="checkbox" style="margin-top: 30px">
|
||||
<input type="checkbox" id="manual" data-group="service" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="manual">Manage my channels manually</label>
|
||||
</div>
|
||||
<div>
|
||||
<p id="errorText" style="color:red"></p>
|
||||
</div>
|
||||
<button class="push-button" style="margin-top: 60px" id="backupBtn">
|
||||
Next
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="js/liquidity.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
<script src="js/liquidity.js"></script>
|
||||
<script>
|
||||
document.getElementById("backupBtn").onclick = (e) => {
|
||||
const automate = document.getElementById('automate').checked
|
||||
const manual = document.getElementById('manual').checked
|
||||
if (!automate && !manual) {
|
||||
document.getElementById('errorText').innerText = 'Please select an option'
|
||||
return
|
||||
}
|
||||
if (automate && manual) {
|
||||
document.getElementById('errorText').innerText = 'Please select only one option'
|
||||
return
|
||||
}
|
||||
if (automate) {
|
||||
localStorage.setItem('wizard/liquidity', 'automate')
|
||||
} else {
|
||||
localStorage.setItem('wizard/liquidity', 'manual')
|
||||
}
|
||||
location.href = 'backup.html'
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
232
static/seed.html
232
static/seed.html
|
|
@ -1,180 +1,74 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat"
|
||||
/>
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/seed.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img
|
||||
src="img/pub_logo.png"
|
||||
width="38px"
|
||||
height="auto"
|
||||
alt="Lightning Pub logo"
|
||||
/>
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<button class="icon-button back-button" onclick="history.back()">
|
||||
<img src="img/back.svg" alt="" />
|
||||
</button>
|
||||
<h2>Seed Phrase</h2>
|
||||
<p class="header-title">
|
||||
Store your seed phrase somewhere safe, you will need it if something ever goes wrong with your node hard drive.
|
||||
</p>
|
||||
</section>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/seed.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
|
||||
<div class="line"></div>
|
||||
<body>
|
||||
<header>
|
||||
<img src="img/pub_logo.png" width="38px" height="auto" alt="Lightning Pub logo" />
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<section class="setup-content">
|
||||
<div class="seed-box-container blur-filter" id="seed-box-container">
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
<div class="seed-box">
|
||||
<span>1</span>
|
||||
<span>albert</span>
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<button class="icon-button back-button" onclick="history.back()">
|
||||
<img src="img/back.svg" alt="" />
|
||||
</button>
|
||||
<h2>Seed Phrase</h2>
|
||||
<p class="header-title">
|
||||
Store your seed phrase somewhere safe, you will need it if something ever goes wrong with your node hard drive.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<button class="reveal-button" id="reveal-button">Click To Reveal</button>
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="checkbox-container">
|
||||
<div class="checkbox" style="margin-top: 12px">
|
||||
<input type="checkbox" id="copied" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="copied">
|
||||
I have copied this somewhere safe
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
id="next-button"
|
||||
class="push-button hidden-button"
|
||||
onclick="location.href='connect.html'"
|
||||
style="margin-top: 60px"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
<section class="setup-content">
|
||||
<div class="seed-box-container blur-filter" id="seed-box-container">
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
<button class="reveal-button" id="reveal-button">Click To Reveal</button>
|
||||
|
||||
<div class="checkbox-container">
|
||||
<div class="checkbox" style="margin-top: 12px">
|
||||
<input type="checkbox" id="copied" />
|
||||
<div class="checkbox-shape"></div>
|
||||
<label for="copied">
|
||||
I have copied this somewhere safe
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
<button id="next-button" class="push-button hidden-button" style="margin-top: 60px">
|
||||
Next
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="js/seed.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<footer>
|
||||
<div class="footer-text">
|
||||
<div>By proceeding you acknowledge that this is</div>
|
||||
<div>bleeding-edge software, and agree to the providers</div>
|
||||
<div>
|
||||
<span style="color: #c434e0">terms</span> regarding any services
|
||||
herein.
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
|
||||
<script src="js/seed.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
174
static/status.html
Normal file
174
static/status.html
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/status.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<img src="img/pub_logo.png" width="38px" height="auto" alt="Lightning Pub logo" />
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<h2>Node Status</h2>
|
||||
<p class="header-title"></p>
|
||||
</section>
|
||||
|
||||
<div class="line" style="width: 100%;"></div>
|
||||
|
||||
<section class="node-status">
|
||||
<p id="errorText" style="color:red"></p>
|
||||
<div>
|
||||
<div class="status-element" style="margin-top: 15px;">
|
||||
<div style="text-align: left;">Public Node Name:</div>
|
||||
<div class="fc-grey editable-content">
|
||||
<div class="show-nodey" style="display: flex; flex-direction: column; display: none;">
|
||||
<input type="text" value="" name="show-nodey" placeholder="Nodey McNodeFace" />
|
||||
<div style="display: flex;justify-content: end;">
|
||||
<button class="small-btn" id="cancel-show-nodey">Cancel</button>
|
||||
<button class="small-btn" id="save-show-nodey">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="show-nodey-text">Nodey McNodeFace</div>
|
||||
<div class="question-box">
|
||||
<button class="icon-button" id="show-nodey">
|
||||
<img src="img/pencil.svg" style="cursor: pointer;" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-element" style="margin-top: 15px;">
|
||||
<div style="text-align: left;">Nostr Relay:</div>
|
||||
<div class="fc-grey editable-content">
|
||||
<div class="show-nostr" style="display: flex; flex-direction: column; display: none;">
|
||||
<input type="text" value="" name="show-nostr" placeholder="wss://relay.lightning.pub" />
|
||||
<div style="display: flex;justify-content: end;">
|
||||
<button class="small-btn" id="cancel-show-nostr">Cancel</button>
|
||||
<button class="small-btn" id="save-show-nostr">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="show-nostr-text">wss://relay.lightning.pub</div>
|
||||
<div class="question-box">
|
||||
<button class="icon-button" id="show-nostr">
|
||||
<img src="img/pencil.svg" style="cursor: pointer;" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-element" style="margin-top: 15px;">
|
||||
<div>Administrator:</div>
|
||||
<div id="adminNpub" style="line-break: anywhere;">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: end;padding-right: 12px;">
|
||||
<div class="marked" id="show-reset" style="text-decoration: underline; margin-top: 5px;position: relative;">
|
||||
Reset
|
||||
<div class="watchdog-status">
|
||||
<button class="icon-button" id="show-question">
|
||||
<img src="img/question.svg" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reset-box">
|
||||
<div style="width: 100%;height: 100%;position: relative;">
|
||||
<button class="icon-button close-button" id="close-reset-box">
|
||||
<img src="img/close.svg" alt="">
|
||||
</button>
|
||||
<div class="reset-box-content" id="reset-content">
|
||||
</div>
|
||||
<div class="continue-button-container">
|
||||
<div class="continue-button" id="">Continue</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 40px;">
|
||||
<div class="status-element">
|
||||
<div>Relay Status:</div>
|
||||
<div id="relayStatus">
|
||||
<span class="yellow-dot">●</span> Loading...
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-element">
|
||||
<div>Lightning Status:</div>
|
||||
<div id="lndStatus">
|
||||
<span class="yellow-dot">●</span> Loading...
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-element">
|
||||
<div style="position: relative;">
|
||||
Watchdog Status:
|
||||
<div class="watchdog-status">
|
||||
<button class="icon-button" id="show-question">
|
||||
<img src="img/question.svg" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="watchdog-status">
|
||||
<span class="green-dot">●</span> Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="font-size: 13px; text-align: left;">Guest Invitation Link:</div>
|
||||
<a href="https://my.shockwallet.app/invite/nprofile12345678899988" target="_blank"
|
||||
style="font-size: 11px;line-break: anywhere;" id="inviteLinkHttp" class="invite-link">
|
||||
https://my.shockwallet.app/invite/nprofile12345678899988
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-text" style="width: 80%">
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
|
||||
<script src="js/status.js"></script>
|
||||
<script>
|
||||
const fetchInfo = async () => {
|
||||
console.log("fewtching...")
|
||||
const res = await fetch("/wizard/service_state")
|
||||
console.log(res)
|
||||
if (res.status !== 200) {
|
||||
document.getElementById('errorText').innerText = "failed to get state info"
|
||||
return
|
||||
}
|
||||
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 = `<span class="${j.relay_connected ? 'green-dot' : 'red-dot'}">●</span> ${j.relay_connected ? 'Connected' : 'Disconnected'}`
|
||||
document.getElementById("lndStatus").innerHTML = `<span class="${j.lnd_state === 'ONLINE' ? 'green-dot' : 'red-dot'}">●</span> ${j.lnd_state}`
|
||||
document.getElementById("watchdog-status").innerHTML = `<span class="${j.watchdog_ok ? 'green-dot' : 'red-dot'}">●</span> ${j.watchdog_ok ? 'No Alerts' : 'ALERT!!'}`
|
||||
document.getElementById("inviteLinkHttp").href = `https://my.shockwallet.app/invite/${j.nprofile}`
|
||||
document.getElementById("inviteLinkHttp").innerHTML = `https://my.shockwallet.app/invite/${j.nprofile}`
|
||||
}
|
||||
try {
|
||||
fetchInfo()
|
||||
} catch (e) { console.log({ e }) }
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat"
|
||||
/>
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/status.css" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>Lightning.Pub</title>
|
||||
<meta name="description" content="Lightning for Everyone" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img
|
||||
src="img/pub_logo.png"
|
||||
width="38px"
|
||||
height="auto"
|
||||
alt="Lightning Pub logo"
|
||||
/>
|
||||
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="setup-header">
|
||||
<h2>Node Status</h2>
|
||||
<p class="header-title"></p>
|
||||
</section>
|
||||
|
||||
<div class="line" style="width: 100%;"></div>
|
||||
|
||||
<section class="node-status">
|
||||
<div>
|
||||
<div class="status-element" style="margin-top: 15px;">
|
||||
<div style="text-align: left;">Public Node Name:</div>
|
||||
<div class="fc-grey editable-content">
|
||||
<div class="show-nodey" style="display: flex; flex-direction: column; display: none;">
|
||||
<input type="text" value="" name="show-nodey" placeholder="Nodey McNodeFace" />
|
||||
<div style="display: flex;justify-content: end;">
|
||||
<button class="small-btn" id="cancel-show-nodey">Cancel</button>
|
||||
<button class="small-btn" id="save-show-nodey">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="show-nodey-text">Nodey McNodeFace</div>
|
||||
<div class="question-box">
|
||||
<button class="icon-button" id="show-nodey">
|
||||
<img src="img/pencil.svg" style="cursor: pointer;" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-element" style="margin-top: 15px;">
|
||||
<div style="text-align: left;">Nostr Relay:</div>
|
||||
<div class="fc-grey editable-content">
|
||||
<div class="show-nostr" style="display: flex; flex-direction: column; display: none;">
|
||||
<input type="text" value="" name="show-nostr" placeholder="wss://relay.lightning.pub" />
|
||||
<div style="display: flex;justify-content: end;">
|
||||
<button class="small-btn" id="cancel-show-nostr">Cancel</button>
|
||||
<button class="small-btn" id="save-show-nostr">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="show-nostr-text">wss://relay.lightning.pub</div>
|
||||
<div class="question-box">
|
||||
<button class="icon-button" id="show-nostr">
|
||||
<img src="img/pencil.svg" style="cursor: pointer;" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-element" style="margin-top: 15px;">
|
||||
<div>Administrator:</div>
|
||||
<div>
|
||||
npub12334556677889990
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: end;padding-right: 12px;">
|
||||
<div class="marked" id="show-reset" style="text-decoration: underline; margin-top: 5px;position: relative;">Reset
|
||||
<div class="watchdog-status">
|
||||
<button class="icon-button" id="show-question">
|
||||
<img src="img/question.svg" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reset-box">
|
||||
<div style="width: 100%;height: 100%;position: relative;">
|
||||
<button class="icon-button close-button" id="close-reset-box">
|
||||
<img src="img/close.svg" alt="">
|
||||
</button>
|
||||
<div class="reset-box-content" id="reset-content">
|
||||
</div>
|
||||
<div class="continue-button-container">
|
||||
<div class="continue-button" id="">Continue</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 40px;">
|
||||
<div class="status-element">
|
||||
<div>Relay Status:</div>
|
||||
<div>
|
||||
<span class="green-dot">●</span> Connected
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-element">
|
||||
<div>Lightning Status:</div>
|
||||
<div>
|
||||
<span class="yellow-dot">●</span> Syncing
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-element">
|
||||
<div style="position: relative;">
|
||||
Watchdog Status:
|
||||
<div class="watchdog-status">
|
||||
<button class="icon-button" id="show-question">
|
||||
<img src="img/question.svg" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="green-dot">●</span> No Alarms
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="font-size: 13px; text-align: left;">Guest Invitation Link:</div>
|
||||
<a href="https://my.shockwallet.app/invite/nprofile12345678899988" target="_blank" style="font-size: 11px;" class="invite-link">
|
||||
https://my.shockwallet.app/invite/nprofile12345678899988
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-text" style="width: 80%">
|
||||
<div class="line"></div>
|
||||
<a href="https://docs.shock.network" class="marked need-help">Need Help?</a>
|
||||
</footer>
|
||||
|
||||
<script src="js/status.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue