From 1f5c3041bdfab0b29079a1d9bbecaccab555fe57 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Wed, 4 Dec 2024 20:27:06 +0000 Subject: [PATCH 01/35] custom ofers --- package-lock.json | 10 +- package.json | 2 +- proto/autogenerated/client.md | 82 ++++++ proto/autogenerated/go/http_client.go | 126 +++++++++ proto/autogenerated/go/types.go | 25 ++ proto/autogenerated/ts/express_server.ts | 165 ++++++++++++ proto/autogenerated/ts/http_client.ts | 64 +++++ proto/autogenerated/ts/nostr_client.ts | 67 +++++ proto/autogenerated/ts/nostr_transport.ts | 135 ++++++++++ proto/autogenerated/ts/types.ts | 155 +++++++++++- proto/service/methods.proto | 36 +++ proto/service/structs.proto | 27 ++ src/nostrMiddleware.ts | 2 +- src/services/main/applicationManager.ts | 5 +- src/services/main/index.ts | 69 +---- src/services/main/offerManager.ts | 239 ++++++++++++++++++ src/services/serverMethods/index.ts | 27 ++ src/services/storage/entity/UserOffer.ts | 37 +++ .../storage/entity/UserReceivingInvoice.ts | 9 + src/services/storage/index.ts | 3 + src/services/storage/offerStorage.ts | 41 +++ src/services/storage/paymentStorage.ts | 6 +- 22 files changed, 1254 insertions(+), 78 deletions(-) create mode 100644 src/services/main/offerManager.ts create mode 100644 src/services/storage/entity/UserOffer.ts create mode 100644 src/services/storage/offerStorage.ts diff --git a/package-lock.json b/package-lock.json index 6501617d..223d36de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "grpc-tools": "^1.12.4", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", - "nostr-tools": "github:shocknet/nostr-tools#da188cd4bd195f44cc690074a3898f354ae85100", + "nostr-tools": "github:shocknet/nostr-tools#27575ffb69d615691242df433a0ccc063f6b8346", "pg": "^8.4.0", "reflect-metadata": "^0.2.2", "rimraf": "^3.0.2", @@ -3796,9 +3796,9 @@ } }, "node_modules/nostr-tools": { - "version": "2.8.0", - "resolved": "git+ssh://git@github.com/shocknet/nostr-tools.git#da188cd4bd195f44cc690074a3898f354ae85100", - "integrity": "sha512-kc41K75rXEnLhqIwlQmjaGsZ9yYTbyP8VW7B2Q+0U/pqaMyt25Nt0QCWiIYS04m0sanvD77OhmddvI1s2ntKog==", + "version": "2.10.4", + "resolved": "git+ssh://git@github.com/shocknet/nostr-tools.git#27575ffb69d615691242df433a0ccc063f6b8346", + "integrity": "sha512-ZQxr1yalFLi5coqG5pHWmjHGehLgCZbQusE/59mre/CgqrFMbGJY77AGTyhDnaGgqRc7B/UJnIvqGVVKhTVRmQ==", "license": "Unlicense", "dependencies": { "@noble/ciphers": "^0.5.1", @@ -3809,7 +3809,7 @@ "@scure/bip39": "1.2.1" }, "optionalDependencies": { - "nostr-wasm": "v0.1.0" + "nostr-wasm": "0.1.0" }, "peerDependencies": { "typescript": ">=5.0.0" diff --git a/package.json b/package.json index e9c13a0e..8720fc99 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "grpc-tools": "^1.12.4", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", - "nostr-tools": "github:shocknet/nostr-tools#da188cd4bd195f44cc690074a3898f354ae85100", + "nostr-tools": "github:shocknet/nostr-tools#27575ffb69d615691242df433a0ccc063f6b8346", "pg": "^8.4.0", "reflect-metadata": "^0.2.2", "rimraf": "^3.0.2", diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index b2a729b4..4c5b223f 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -28,6 +28,11 @@ The nostr server will send back a message response, and inside the body there wi - input: [AddProductRequest](#AddProductRequest) - output: [Product](#Product) +- AddUserOffer + - auth type: __User__ + - input: [OfferConfig](#OfferConfig) + - output: [OfferId](#OfferId) + - AuthApp - auth type: __Admin__ - input: [AuthAppRequest](#AuthAppRequest) @@ -68,6 +73,11 @@ The nostr server will send back a message response, and inside the body there wi - input: [DecodeInvoiceRequest](#DecodeInvoiceRequest) - output: [DecodeInvoiceResponse](#DecodeInvoiceResponse) +- DeleteUserOffer + - auth type: __User__ + - input: [OfferId](#OfferId) + - This methods has an __empty__ __response__ body + - EditDebit - auth type: __User__ - input: [DebitAuthorizationRequest](#DebitAuthorizationRequest) @@ -153,6 +163,16 @@ The nostr server will send back a message response, and inside the body there wi - This methods has an __empty__ __request__ body - output: [UserInfo](#UserInfo) +- GetUserOffer + - auth type: __User__ + - input: [OfferId](#OfferId) + - output: [OfferConfig](#OfferConfig) + +- GetUserOffers + - auth type: __User__ + - This methods has an __empty__ __request__ body + - output: [UserOffers](#UserOffers) + - GetUserOperations - auth type: __User__ - input: [GetUserOperationsRequest](#GetUserOperationsRequest) @@ -225,6 +245,11 @@ The nostr server will send back a message response, and inside the body there wi - input: [UpdateChannelPolicyRequest](#UpdateChannelPolicyRequest) - This methods has an __empty__ __response__ body +- UpdateUserOffer + - auth type: __User__ + - input: [OfferConfig](#OfferConfig) + - This methods has an __empty__ __response__ body + - UseInviteLink - auth type: __GuestWithPub__ - input: [UseInviteLinkRequest](#UseInviteLinkRequest) @@ -311,6 +336,13 @@ The nostr server will send back a message response, and inside the body there wi - input: [AddProductRequest](#AddProductRequest) - output: [Product](#Product) +- AddUserOffer + - auth type: __User__ + - http method: __post__ + - http route: __/api/user/offer/add__ + - input: [OfferConfig](#OfferConfig) + - output: [OfferId](#OfferId) + - AuthApp - auth type: __Admin__ - http method: __post__ @@ -367,6 +399,13 @@ The nostr server will send back a message response, and inside the body there wi - input: [DecodeInvoiceRequest](#DecodeInvoiceRequest) - output: [DecodeInvoiceResponse](#DecodeInvoiceResponse) +- DeleteUserOffer + - auth type: __User__ + - http method: __post__ + - http route: __/api/user/offer/delete__ + - input: [OfferId](#OfferId) + - This methods has an __empty__ __response__ body + - EditDebit - auth type: __User__ - http method: __post__ @@ -539,6 +578,20 @@ The nostr server will send back a message response, and inside the body there wi - This methods has an __empty__ __request__ body - output: [UserInfo](#UserInfo) +- GetUserOffer + - auth type: __User__ + - http method: __get__ + - http route: __/api/user/offer/get__ + - input: [OfferId](#OfferId) + - output: [OfferConfig](#OfferConfig) + +- GetUserOffers + - auth type: __User__ + - http method: __get__ + - http route: __/api/user/offers/get__ + - This methods has an __empty__ __request__ body + - output: [UserOffers](#UserOffers) + - GetUserOperations - auth type: __User__ - http method: __post__ @@ -733,6 +786,13 @@ The nostr server will send back a message response, and inside the body there wi - input: [UpdateChannelPolicyRequest](#UpdateChannelPolicyRequest) - This methods has an __empty__ __response__ body +- UpdateUserOffer + - auth type: __User__ + - http method: __post__ + - http route: __/api/user/offer/update__ + - input: [OfferConfig](#OfferConfig) + - This methods has an __empty__ __response__ body + - UseInviteLink - auth type: __GuestWithPub__ - http method: __post__ @@ -764,6 +824,8 @@ The nostr server will send back a message response, and inside the body there wi ### AddAppUserInvoiceRequest - __http_callback_url__: _string_ - __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_ + - __offer_string__: _string_ *this field is optional + - __payer_data__: _[PayerData](#PayerData)_ *this field is optional - __payer_identifier__: _string_ - __receiver_identifier__: _string_ @@ -1056,6 +1118,17 @@ The nostr server will send back a message response, and inside the body there wi ### NewInvoiceResponse - __invoice__: _string_ +### OfferConfig + - __callback_url__: _string_ + - __expected_data__: MAP with key: _string_ and value: _[OfferDataType](#OfferDataType)_ + - __label__: _string_ + - __noffer__: _string_ + - __offer_id__: _string_ + - __price_sats__: _number_ + +### OfferId + - __offer_id__: _string_ + ### OpenChannel - __active__: _boolean_ - __capacity__: _number_ @@ -1106,6 +1179,9 @@ The nostr server will send back a message response, and inside the body there wi - __preimage__: _string_ - __service_fee__: _number_ +### PayerData + - __data__: MAP with key: _string_ and value: _string_ + ### PaymentState - __amount__: _number_ - __network_fee__: _number_ @@ -1201,6 +1277,9 @@ The nostr server will send back a message response, and inside the body there wi - __userId__: _string_ - __user_identifier__: _string_ +### UserOffers + - __offers__: ARRAY of: _[OfferConfig](#OfferConfig)_ + ### UserOperation - __amount__: _number_ - __confirmed__: _boolean_ @@ -1239,6 +1318,9 @@ The nostr server will send back a message response, and inside the body there wi - __MONTH__ - __WEEK__ +### OfferDataType + - __DATA_STRING__ + ### OperationType - __CHAIN_OP__ - __INVOICE_OP__ diff --git a/proto/autogenerated/go/http_client.go b/proto/autogenerated/go/http_client.go index 06a87ff7..0289dfd3 100644 --- a/proto/autogenerated/go/http_client.go +++ b/proto/autogenerated/go/http_client.go @@ -60,6 +60,7 @@ type Client struct { AddAppUserInvoice func(req AddAppUserInvoiceRequest) (*NewInvoiceResponse, error) AddPeer func(req AddPeerRequest) error AddProduct func(req AddProductRequest) (*Product, error) + AddUserOffer func(req OfferConfig) (*OfferId, error) AuthApp func(req AuthAppRequest) (*AuthApp, error) AuthorizeDebit func(req DebitAuthorizationRequest) (*DebitAuthorization, error) BanDebit func(req DebitOperation) error @@ -68,6 +69,7 @@ type Client struct { CloseChannel func(req CloseChannelRequest) (*CloseChannelResponse, error) CreateOneTimeInviteLink func(req CreateOneTimeInviteLinkRequest) (*CreateOneTimeInviteLinkResponse, error) DecodeInvoice func(req DecodeInvoiceRequest) (*DecodeInvoiceResponse, error) + DeleteUserOffer func(req OfferId) error EditDebit func(req DebitAuthorizationRequest) error EncryptionExchange func(req EncryptionExchangeRequest) error EnrollAdminToken func(req EnrollAdminTokenRequest) error @@ -92,6 +94,8 @@ type Client struct { GetSeed func() (*LndSeed, error) GetUsageMetrics func() (*UsageMetrics, error) GetUserInfo func() (*UserInfo, error) + GetUserOffer func(req OfferId) (*OfferConfig, error) + GetUserOffers func() (*UserOffers, error) GetUserOperations func(req GetUserOperationsRequest) (*GetUserOperationsResponse, error) HandleLnurlAddress func(routeParams HandleLnurlAddress_RouteParams) (*LnurlPayInfoResponse, error) HandleLnurlPay func(query HandleLnurlPay_Query) (*HandleLnurlPayResponse, error) @@ -118,6 +122,7 @@ type Client struct { SetMockInvoiceAsPaid func(req SetMockInvoiceAsPaidRequest) error UpdateCallbackUrl func(req CallbackUrl) (*CallbackUrl, error) UpdateChannelPolicy func(req UpdateChannelPolicyRequest) error + UpdateUserOffer func(req OfferConfig) error UseInviteLink func(req UseInviteLinkRequest) error UserHealth func() error } @@ -293,6 +298,35 @@ func NewClient(params ClientParams) *Client { } return &res, nil }, + AddUserOffer: func(req OfferConfig) (*OfferId, error) { + auth, err := params.RetrieveUserAuth() + if err != nil { + return nil, err + } + finalRoute := "/api/user/offer/add" + body, err := json.Marshal(req) + if err != nil { + return nil, err + } + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return nil, err + } + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := OfferId{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, AuthApp: func(req AuthAppRequest) (*AuthApp, error) { auth, err := params.RetrieveAdminAuth() if err != nil { @@ -492,6 +526,30 @@ func NewClient(params ClientParams) *Client { } return &res, nil }, + DeleteUserOffer: func(req OfferId) error { + auth, err := params.RetrieveUserAuth() + if err != nil { + return err + } + finalRoute := "/api/user/offer/delete" + body, err := json.Marshal(req) + if err != nil { + return err + } + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return err + } + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return err + } + if result.Status == "ERROR" { + return fmt.Errorf(result.Reason) + } + return nil + }, EditDebit: func(req DebitAuthorizationRequest) error { auth, err := params.RetrieveUserAuth() if err != nil { @@ -1023,6 +1081,50 @@ func NewClient(params ClientParams) *Client { } return &res, nil }, + GetUserOffer: func(req OfferId) (*OfferConfig, error) { + auth, err := params.RetrieveUserAuth() + if err != nil { + return nil, err + } + finalRoute := "/api/user/offer/get" + resBody, err := doGetRequest(params.BaseURL+finalRoute, auth) + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := OfferConfig{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, + GetUserOffers: func() (*UserOffers, error) { + auth, err := params.RetrieveUserAuth() + if err != nil { + return nil, err + } + finalRoute := "/api/user/offers/get" + resBody, err := doGetRequest(params.BaseURL+finalRoute, auth) + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := UserOffers{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, GetUserOperations: func(req GetUserOperationsRequest) (*GetUserOperationsResponse, error) { auth, err := params.RetrieveUserAuth() if err != nil { @@ -1717,6 +1819,30 @@ func NewClient(params ClientParams) *Client { } return nil }, + UpdateUserOffer: func(req OfferConfig) error { + auth, err := params.RetrieveUserAuth() + if err != nil { + return err + } + finalRoute := "/api/user/offer/update" + body, err := json.Marshal(req) + if err != nil { + return err + } + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return err + } + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return err + } + if result.Status == "ERROR" { + return fmt.Errorf(result.Reason) + } + return nil + }, UseInviteLink: func(req UseInviteLinkRequest) error { auth, err := params.RetrieveGuestWithPubAuth() if err != nil { diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 9b84c889..1881e0be 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -64,6 +64,12 @@ const ( WEEK IntervalType = "WEEK" ) +type OfferDataType string + +const ( + DATA_STRING OfferDataType = "DATA_STRING" +) + type OperationType string const ( @@ -94,6 +100,8 @@ type AddAppRequest struct { type AddAppUserInvoiceRequest struct { Http_callback_url string `json:"http_callback_url"` Invoice_req *NewInvoiceRequest `json:"invoice_req"` + Offer_string string `json:"offer_string"` + Payer_data *PayerData `json:"payer_data"` Payer_identifier string `json:"payer_identifier"` Receiver_identifier string `json:"receiver_identifier"` } @@ -386,6 +394,17 @@ type NewInvoiceRequest struct { type NewInvoiceResponse struct { Invoice string `json:"invoice"` } +type OfferConfig struct { + Callback_url string `json:"callback_url"` + Expected_data map[string]OfferDataType `json:"expected_data"` + Label string `json:"label"` + Noffer string `json:"noffer"` + Offer_id string `json:"offer_id"` + Price_sats int64 `json:"price_sats"` +} +type OfferId struct { + Offer_id string `json:"offer_id"` +} type OpenChannel struct { Active bool `json:"active"` Capacity int64 `json:"capacity"` @@ -436,6 +455,9 @@ type PayInvoiceResponse struct { Preimage string `json:"preimage"` Service_fee int64 `json:"service_fee"` } +type PayerData struct { + Data map[string]string `json:"data"` +} type PaymentState struct { Amount int64 `json:"amount"` Network_fee int64 `json:"network_fee"` @@ -531,6 +553,9 @@ type UserInfo struct { Userid string `json:"userId"` User_identifier string `json:"user_identifier"` } +type UserOffers struct { + Offers []OfferConfig `json:"offers"` +} type UserOperation struct { Amount int64 `json:"amount"` Confirmed bool `json:"confirmed"` diff --git a/proto/autogenerated/ts/express_server.ts b/proto/autogenerated/ts/express_server.ts index cedcfc7d..603d93d5 100644 --- a/proto/autogenerated/ts/express_server.ts +++ b/proto/autogenerated/ts/express_server.ts @@ -166,6 +166,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.AddUserOffer) throw new Error('method: AddUserOffer is not implemented') + app.post('/api/user/offer/add', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'AddUserOffer', 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.AddUserOffer) throw new Error('method: AddUserOffer 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.OfferConfigValidate(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 + const response = await methods.AddUserOffer({rpcName:'AddUserOffer', ctx:authContext , req: request}) + 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.AuthApp) throw new Error('method: AuthApp is not implemented') app.post('/api/admin/app/auth', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'AuthApp', batch: false, nostr: false, batchSize: 0} @@ -287,6 +309,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'AddUserOffer': + if (!methods.AddUserOffer) { + throw new Error('method AddUserOffer not found' ) + } else { + const error = Types.OfferConfigValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + const res = await methods.AddUserOffer({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'AuthorizeDebit': if (!methods.AuthorizeDebit) { throw new Error('method AuthorizeDebit not found' ) @@ -323,6 +357,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'DeleteUserOffer': + if (!methods.DeleteUserOffer) { + throw new Error('method DeleteUserOffer not found' ) + } else { + const error = Types.OfferIdValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + await methods.DeleteUserOffer({...operation, ctx}); responses.push({ status: 'OK' }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'EditDebit': if (!methods.EditDebit) { throw new Error('method EditDebit not found' ) @@ -409,6 +455,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'GetUserOffer': + if (!methods.GetUserOffer) { + throw new Error('method GetUserOffer not found' ) + } else { + const error = Types.OfferIdValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + const res = await methods.GetUserOffer({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break + case 'GetUserOffers': + if (!methods.GetUserOffers) { + throw new Error('method GetUserOffers not found' ) + } else { + opStats.validate = opStats.guard + const res = await methods.GetUserOffers({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'GetUserOperations': if (!methods.GetUserOperations) { throw new Error('method GetUserOperations not found' ) @@ -515,6 +583,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'UpdateUserOffer': + if (!methods.UpdateUserOffer) { + throw new Error('method UpdateUserOffer not found' ) + } else { + const error = Types.OfferConfigValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + await methods.UpdateUserOffer({...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' ) @@ -601,6 +681,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.DeleteUserOffer) throw new Error('method: DeleteUserOffer is not implemented') + app.post('/api/user/offer/delete', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'DeleteUserOffer', 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.DeleteUserOffer) throw new Error('method: DeleteUserOffer 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.OfferIdValidate(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.DeleteUserOffer({rpcName:'DeleteUserOffer', 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.EditDebit) throw new Error('method: EditDebit is not implemented') app.post('/api/user/debit/edit', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'EditDebit', batch: false, nostr: false, batchSize: 0} @@ -1011,6 +1113,47 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { opts.metricsCallback([{ ...info, ...stats, ...authContext }]) } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } }) + if (!opts.allowNotImplementedMethods && !methods.GetUserOffer) throw new Error('method: GetUserOffer is not implemented') + app.get('/api/user/offer/get', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'GetUserOffer', 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.GetUserOffer) throw new Error('method: GetUserOffer 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.OfferIdValidate(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 + const response = await methods.GetUserOffer({rpcName:'GetUserOffer', ctx:authContext , req: request}) + 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.GetUserOffers) throw new Error('method: GetUserOffers is not implemented') + app.get('/api/user/offers/get', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'GetUserOffers', 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.GetUserOffers) throw new Error('method: GetUserOffers is not implemented') + const authContext = await opts.UserAuthGuard(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.GetUserOffers({rpcName:'GetUserOffers', 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.GetUserOperations) throw new Error('method: GetUserOperations is not implemented') app.post('/api/user/operations', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'GetUserOperations', batch: false, nostr: false, batchSize: 0} @@ -1565,6 +1708,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.UpdateUserOffer) throw new Error('method: UpdateUserOffer is not implemented') + app.post('/api/user/offer/update', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'UpdateUserOffer', 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.UpdateUserOffer) throw new Error('method: UpdateUserOffer 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.OfferConfigValidate(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.UpdateUserOffer({rpcName:'UpdateUserOffer', 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.UseInviteLink) throw new Error('method: UseInviteLink is not implemented') app.post('/api/guest/invite', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'UseInviteLink', batch: false, nostr: false, batchSize: 0} diff --git a/proto/autogenerated/ts/http_client.ts b/proto/autogenerated/ts/http_client.ts index 1bc428a1..b2ed71f6 100644 --- a/proto/autogenerated/ts/http_client.ts +++ b/proto/autogenerated/ts/http_client.ts @@ -98,6 +98,20 @@ export default (params: ClientParams) => ({ } return { status: 'ERROR', reason: 'invalid response' } }, + AddUserOffer: async (request: Types.OfferConfig): Promise => { + const auth = await params.retrieveUserAuth() + if (auth === null) throw new Error('retrieveUserAuth() returned null') + let finalRoute = '/api/user/offer/add' + 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') { + const result = data + if(!params.checkResult) return { status: 'OK', ...result } + const error = Types.OfferIdValidate(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 => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') @@ -204,6 +218,17 @@ export default (params: ClientParams) => ({ } return { status: 'ERROR', reason: 'invalid response' } }, + DeleteUserOffer: async (request: Types.OfferId): Promise => { + const auth = await params.retrieveUserAuth() + if (auth === null) throw new Error('retrieveUserAuth() returned null') + let finalRoute = '/api/user/offer/delete' + 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' } + }, EditDebit: async (request: Types.DebitAuthorizationRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') @@ -483,6 +508,34 @@ export default (params: ClientParams) => ({ } return { status: 'ERROR', reason: 'invalid response' } }, + GetUserOffer: async (request: Types.OfferId): Promise => { + const auth = await params.retrieveUserAuth() + if (auth === null) throw new Error('retrieveUserAuth() returned null') + let finalRoute = '/api/user/offer/get' + 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.OfferConfigValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, + GetUserOffers: async (): Promise => { + const auth = await params.retrieveUserAuth() + if (auth === null) throw new Error('retrieveUserAuth() returned null') + let finalRoute = '/api/user/offers/get' + 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.UserOffersValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') @@ -821,6 +874,17 @@ export default (params: ClientParams) => ({ } return { status: 'ERROR', reason: 'invalid response' } }, + UpdateUserOffer: async (request: Types.OfferConfig): Promise => { + const auth = await params.retrieveUserAuth() + if (auth === null) throw new Error('retrieveUserAuth() returned null') + let finalRoute = '/api/user/offer/update' + 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' } + }, UseInviteLink: async (request: Types.UseInviteLinkRequest): Promise => { const auth = await params.retrieveGuestWithPubAuth() if (auth === null) throw new Error('retrieveGuestWithPubAuth() returned null') diff --git a/proto/autogenerated/ts/nostr_client.ts b/proto/autogenerated/ts/nostr_client.ts index 6a2c8d5d..94dd8852 100644 --- a/proto/autogenerated/ts/nostr_client.ts +++ b/proto/autogenerated/ts/nostr_client.ts @@ -54,6 +54,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ } return { status: 'ERROR', reason: 'invalid response' } }, + AddUserOffer: async (request: Types.OfferConfig): Promise => { + 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:'AddUserOffer',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.OfferIdValidate(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 => { const auth = await params.retrieveNostrAdminAuth() if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null') @@ -167,6 +182,18 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ } return { status: 'ERROR', reason: 'invalid response' } }, + DeleteUserOffer: async (request: Types.OfferId): Promise => { + 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:'DeleteUserOffer',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' } + }, EditDebit: async (request: Types.DebitAuthorizationRequest): Promise => { const auth = await params.retrieveNostrUserAuth() if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') @@ -409,6 +436,34 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ } return { status: 'ERROR', reason: 'invalid response' } }, + GetUserOffer: async (request: Types.OfferId): Promise => { + const auth = await params.retrieveNostrUserAuth() + if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') + const nostrRequest: NostrRequest = {} + const data = await send(params.pubDestination, {rpcName:'GetUserOffer',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.OfferConfigValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, + GetUserOffers: async (): Promise => { + const auth = await params.retrieveNostrUserAuth() + if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') + const nostrRequest: NostrRequest = {} + const data = await send(params.pubDestination, {rpcName:'GetUserOffers',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.UserOffersValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise => { const auth = await params.retrieveNostrUserAuth() if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') @@ -606,6 +661,18 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ } return { status: 'ERROR', reason: 'invalid response' } }, + UpdateUserOffer: async (request: Types.OfferConfig): Promise => { + 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:'UpdateUserOffer',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' } + }, UseInviteLink: async (request: Types.UseInviteLinkRequest): Promise => { const auth = await params.retrieveNostrGuestWithPubAuth() if (auth === null) throw new Error('retrieveNostrGuestWithPubAuth() returned null') diff --git a/proto/autogenerated/ts/nostr_transport.ts b/proto/autogenerated/ts/nostr_transport.ts index 48f824a7..e651e11f 100644 --- a/proto/autogenerated/ts/nostr_transport.ts +++ b/proto/autogenerated/ts/nostr_transport.ts @@ -80,6 +80,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 'AddUserOffer': + try { + if (!methods.AddUserOffer) throw new Error('method: AddUserOffer 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.OfferConfigValidate(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.AddUserOffer({rpcName:'AddUserOffer', 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') @@ -175,6 +191,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'AddUserOffer': + if (!methods.AddUserOffer) { + throw new Error('method not defined: AddUserOffer') + } else { + const error = Types.OfferConfigValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + const res = await methods.AddUserOffer({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'AuthorizeDebit': if (!methods.AuthorizeDebit) { throw new Error('method not defined: AuthorizeDebit') @@ -211,6 +239,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'DeleteUserOffer': + if (!methods.DeleteUserOffer) { + throw new Error('method not defined: DeleteUserOffer') + } else { + const error = Types.OfferIdValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + await methods.DeleteUserOffer({...operation, ctx}); responses.push({ status: 'OK' }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'EditDebit': if (!methods.EditDebit) { throw new Error('method not defined: EditDebit') @@ -297,6 +337,28 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'GetUserOffer': + if (!methods.GetUserOffer) { + throw new Error('method not defined: GetUserOffer') + } else { + const error = Types.OfferIdValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + const res = await methods.GetUserOffer({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break + case 'GetUserOffers': + if (!methods.GetUserOffers) { + throw new Error('method not defined: GetUserOffers') + } else { + opStats.validate = opStats.guard + const res = await methods.GetUserOffers({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'GetUserOperations': if (!methods.GetUserOperations) { throw new Error('method not defined: GetUserOperations') @@ -403,6 +465,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'UpdateUserOffer': + if (!methods.UpdateUserOffer) { + throw new Error('method not defined: UpdateUserOffer') + } else { + const error = Types.OfferConfigValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + await methods.UpdateUserOffer({...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') @@ -471,6 +545,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 'DeleteUserOffer': + try { + if (!methods.DeleteUserOffer) throw new Error('method: DeleteUserOffer 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.OfferIdValidate(request) + stats.validate = process.hrtime.bigint() + if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback) + await methods.DeleteUserOffer({rpcName:'DeleteUserOffer', 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 'EditDebit': try { if (!methods.EditDebit) throw new Error('method: EditDebit is not implemented') @@ -710,6 +800,35 @@ 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 'GetUserOffer': + try { + if (!methods.GetUserOffer) throw new Error('method: GetUserOffer 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.OfferIdValidate(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.GetUserOffer({rpcName:'GetUserOffer', 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 'GetUserOffers': + try { + if (!methods.GetUserOffers) throw new Error('method: GetUserOffers is not implemented') + const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier) + stats.guard = process.hrtime.bigint() + authCtx = authContext + stats.validate = stats.guard + const response = await methods.GetUserOffers({rpcName:'GetUserOffers', 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 'GetUserOperations': try { if (!methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented') @@ -928,6 +1047,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 'UpdateUserOffer': + try { + if (!methods.UpdateUserOffer) throw new Error('method: UpdateUserOffer 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.OfferConfigValidate(request) + stats.validate = process.hrtime.bigint() + if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback) + await methods.UpdateUserOffer({rpcName:'UpdateUserOffer', 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 'UseInviteLink': try { if (!methods.UseInviteLink) throw new Error('method: UseInviteLink is not implemented') diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index ac253234..814ded3a 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -34,8 +34,8 @@ export type UserContext = { app_user_id: string user_id: string } -export type UserMethodInputs = AddProduct_Input | AuthorizeDebit_Input | BanDebit_Input | DecodeInvoice_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UserHealth_Input -export type UserMethodOutputs = AddProduct_Output | AuthorizeDebit_Output | BanDebit_Output | DecodeInvoice_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UserHealth_Output +export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeDebit_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input +export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeDebit_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output export type AuthContext = AdminContext | AppContext | GuestContext | GuestWithPubContext | MetricsContext | UserContext export type AddApp_Input = {rpcName:'AddApp', req: AddAppRequest} @@ -56,6 +56,9 @@ export type AddPeer_Output = ResultError | { status: 'OK' } export type AddProduct_Input = {rpcName:'AddProduct', req: AddProductRequest} export type AddProduct_Output = ResultError | ({ status: 'OK' } & Product) +export type AddUserOffer_Input = {rpcName:'AddUserOffer', req: OfferConfig} +export type AddUserOffer_Output = ResultError | ({ status: 'OK' } & OfferId) + export type AuthApp_Input = {rpcName:'AuthApp', req: AuthAppRequest} export type AuthApp_Output = ResultError | ({ status: 'OK' } & AuthApp) @@ -80,6 +83,9 @@ export type CreateOneTimeInviteLink_Output = ResultError | ({ status: 'OK' } & C export type DecodeInvoice_Input = {rpcName:'DecodeInvoice', req: DecodeInvoiceRequest} export type DecodeInvoice_Output = ResultError | ({ status: 'OK' } & DecodeInvoiceResponse) +export type DeleteUserOffer_Input = {rpcName:'DeleteUserOffer', req: OfferId} +export type DeleteUserOffer_Output = ResultError | { status: 'OK' } + export type EditDebit_Input = {rpcName:'EditDebit', req: DebitAuthorizationRequest} export type EditDebit_Output = ResultError | { status: 'OK' } @@ -158,6 +164,12 @@ export type GetUsageMetrics_Output = ResultError | ({ status: 'OK' } & UsageMetr export type GetUserInfo_Input = {rpcName:'GetUserInfo'} export type GetUserInfo_Output = ResultError | ({ status: 'OK' } & UserInfo) +export type GetUserOffer_Input = {rpcName:'GetUserOffer', req: OfferId} +export type GetUserOffer_Output = ResultError | ({ status: 'OK' } & OfferConfig) + +export type GetUserOffers_Input = {rpcName:'GetUserOffers'} +export type GetUserOffers_Output = ResultError | ({ status: 'OK' } & UserOffers) + export type GetUserOperations_Input = {rpcName:'GetUserOperations', req: GetUserOperationsRequest} export type GetUserOperations_Output = ResultError | ({ status: 'OK' } & GetUserOperationsResponse) @@ -252,6 +264,9 @@ export type UpdateCallbackUrl_Output = ResultError | ({ status: 'OK' } & Callbac export type UpdateChannelPolicy_Input = {rpcName:'UpdateChannelPolicy', req: UpdateChannelPolicyRequest} export type UpdateChannelPolicy_Output = ResultError | { status: 'OK' } +export type UpdateUserOffer_Input = {rpcName:'UpdateUserOffer', req: OfferConfig} +export type UpdateUserOffer_Output = ResultError | { status: 'OK' } + export type UseInviteLink_Input = {rpcName:'UseInviteLink', req: UseInviteLinkRequest} export type UseInviteLink_Output = ResultError | { status: 'OK' } @@ -265,6 +280,7 @@ export type ServerMethods = { AddAppUserInvoice?: (req: AddAppUserInvoice_Input & {ctx: AppContext }) => Promise AddPeer?: (req: AddPeer_Input & {ctx: AdminContext }) => Promise AddProduct?: (req: AddProduct_Input & {ctx: UserContext }) => Promise + AddUserOffer?: (req: AddUserOffer_Input & {ctx: UserContext }) => Promise AuthApp?: (req: AuthApp_Input & {ctx: AdminContext }) => Promise AuthorizeDebit?: (req: AuthorizeDebit_Input & {ctx: UserContext }) => Promise BanDebit?: (req: BanDebit_Input & {ctx: UserContext }) => Promise @@ -272,6 +288,7 @@ export type ServerMethods = { CloseChannel?: (req: CloseChannel_Input & {ctx: AdminContext }) => Promise CreateOneTimeInviteLink?: (req: CreateOneTimeInviteLink_Input & {ctx: AdminContext }) => Promise DecodeInvoice?: (req: DecodeInvoice_Input & {ctx: UserContext }) => Promise + DeleteUserOffer?: (req: DeleteUserOffer_Input & {ctx: UserContext }) => Promise EditDebit?: (req: EditDebit_Input & {ctx: UserContext }) => Promise EncryptionExchange?: (req: EncryptionExchange_Input & {ctx: GuestContext }) => Promise EnrollAdminToken?: (req: EnrollAdminToken_Input & {ctx: UserContext }) => Promise @@ -296,6 +313,8 @@ export type ServerMethods = { GetSeed?: (req: GetSeed_Input & {ctx: AdminContext }) => Promise GetUsageMetrics?: (req: GetUsageMetrics_Input & {ctx: MetricsContext }) => Promise GetUserInfo?: (req: GetUserInfo_Input & {ctx: UserContext }) => Promise + GetUserOffer?: (req: GetUserOffer_Input & {ctx: UserContext }) => Promise + GetUserOffers?: (req: GetUserOffers_Input & {ctx: UserContext }) => Promise GetUserOperations?: (req: GetUserOperations_Input & {ctx: UserContext }) => Promise HandleLnurlAddress?: (req: HandleLnurlAddress_Input & {ctx: GuestContext }) => Promise HandleLnurlPay?: (req: HandleLnurlPay_Input & {ctx: GuestContext }) => Promise @@ -322,6 +341,7 @@ export type ServerMethods = { SetMockInvoiceAsPaid?: (req: SetMockInvoiceAsPaid_Input & {ctx: GuestContext }) => Promise UpdateCallbackUrl?: (req: UpdateCallbackUrl_Input & {ctx: UserContext }) => Promise UpdateChannelPolicy?: (req: UpdateChannelPolicy_Input & {ctx: AdminContext }) => Promise + UpdateUserOffer?: (req: UpdateUserOffer_Input & {ctx: UserContext }) => Promise UseInviteLink?: (req: UseInviteLink_Input & {ctx: GuestWithPubContext }) => Promise UserHealth?: (req: UserHealth_Input & {ctx: UserContext }) => Promise } @@ -344,6 +364,13 @@ export const enumCheckIntervalType = (e?: IntervalType): boolean => { for (const v in IntervalType) if (e === v) return true return false } +export enum OfferDataType { + DATA_STRING = 'DATA_STRING', +} +export const enumCheckOfferDataType = (e?: OfferDataType): boolean => { + for (const v in OfferDataType) if (e === v) return true + return false +} export enum OperationType { CHAIN_OP = 'CHAIN_OP', INVOICE_OP = 'INVOICE_OP', @@ -424,14 +451,19 @@ export const AddAppRequestValidate = (o?: AddAppRequest, opts: AddAppRequestOpti export type AddAppUserInvoiceRequest = { http_callback_url: string invoice_req: NewInvoiceRequest + offer_string?: string + payer_data?: PayerData payer_identifier: string receiver_identifier: string } -export const AddAppUserInvoiceRequestOptionalFields: [] = [] +export type AddAppUserInvoiceRequestOptionalField = 'offer_string' | 'payer_data' +export const AddAppUserInvoiceRequestOptionalFields: AddAppUserInvoiceRequestOptionalField[] = ['offer_string', 'payer_data'] export type AddAppUserInvoiceRequestOptions = OptionsBaseMessage & { - checkOptionalsAreSet?: [] + checkOptionalsAreSet?: AddAppUserInvoiceRequestOptionalField[] http_callback_url_CustomCheck?: (v: string) => boolean invoice_req_Options?: NewInvoiceRequestOptions + offer_string_CustomCheck?: (v?: string) => boolean + payer_data_Options?: PayerDataOptions payer_identifier_CustomCheck?: (v: string) => boolean receiver_identifier_CustomCheck?: (v: string) => boolean } @@ -446,6 +478,15 @@ export const AddAppUserInvoiceRequestValidate = (o?: AddAppUserInvoiceRequest, o if (invoice_reqErr !== null) return invoice_reqErr + if ((o.offer_string || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('offer_string')) && typeof o.offer_string !== 'string') return new Error(`${path}.offer_string: is not a string`) + if (opts.offer_string_CustomCheck && !opts.offer_string_CustomCheck(o.offer_string)) return new Error(`${path}.offer_string: custom check failed`) + + if (typeof o.payer_data === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('payer_data')) { + const payer_dataErr = PayerDataValidate(o.payer_data, opts.payer_data_Options, `${path}.payer_data`) + if (payer_dataErr !== null) return payer_dataErr + } + + if (typeof o.payer_identifier !== 'string') return new Error(`${path}.payer_identifier: is not a string`) if (opts.payer_identifier_CustomCheck && !opts.payer_identifier_CustomCheck(o.payer_identifier)) return new Error(`${path}.payer_identifier: custom check failed`) @@ -2201,6 +2242,69 @@ export const NewInvoiceResponseValidate = (o?: NewInvoiceResponse, opts: NewInvo return null } +export type OfferConfig = { + callback_url: string + expected_data: Record + label: string + noffer: string + offer_id: string + price_sats: number +} +export const OfferConfigOptionalFields: [] = [] +export type OfferConfigOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + callback_url_CustomCheck?: (v: string) => boolean + expected_data_CustomCheck?: (v: Record) => boolean + label_CustomCheck?: (v: string) => boolean + noffer_CustomCheck?: (v: string) => boolean + offer_id_CustomCheck?: (v: string) => boolean + price_sats_CustomCheck?: (v: number) => boolean +} +export const OfferConfigValidate = (o?: OfferConfig, opts: OfferConfigOptions = {}, path: string = 'OfferConfig::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.callback_url !== 'string') return new Error(`${path}.callback_url: is not a string`) + if (opts.callback_url_CustomCheck && !opts.callback_url_CustomCheck(o.callback_url)) return new Error(`${path}.callback_url: custom check failed`) + + if (typeof o.expected_data !== 'object' || o.expected_data === null) return new Error(`${path}.expected_data: is not an object or is null`) + for (const key in o.expected_data) { + if (!enumCheckOfferDataType(o.expected_data[key])) return new Error(`${path}.expected_data['${key}']: is not a OfferDataType`) + } + + if (typeof o.label !== 'string') return new Error(`${path}.label: is not a string`) + if (opts.label_CustomCheck && !opts.label_CustomCheck(o.label)) return new Error(`${path}.label: custom check failed`) + + if (typeof o.noffer !== 'string') return new Error(`${path}.noffer: is not a string`) + if (opts.noffer_CustomCheck && !opts.noffer_CustomCheck(o.noffer)) return new Error(`${path}.noffer: custom check failed`) + + if (typeof o.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`) + if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`) + + if (typeof o.price_sats !== 'number') return new Error(`${path}.price_sats: is not a number`) + if (opts.price_sats_CustomCheck && !opts.price_sats_CustomCheck(o.price_sats)) return new Error(`${path}.price_sats: custom check failed`) + + return null +} + +export type OfferId = { + offer_id: string +} +export const OfferIdOptionalFields: [] = [] +export type OfferIdOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + offer_id_CustomCheck?: (v: string) => boolean +} +export const OfferIdValidate = (o?: OfferId, opts: OfferIdOptions = {}, path: string = 'OfferId::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.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`) + if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`) + + return null +} + export type OpenChannel = { active: boolean capacity: number @@ -2482,6 +2586,26 @@ export const PayInvoiceResponseValidate = (o?: PayInvoiceResponse, opts: PayInvo return null } +export type PayerData = { + data: Record +} +export const PayerDataOptionalFields: [] = [] +export type PayerDataOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + data_CustomCheck?: (v: Record) => boolean +} +export const PayerDataValidate = (o?: PayerData, opts: PayerDataOptions = {}, path: string = 'PayerData::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.data !== 'object' || o.data === null) return new Error(`${path}.data: is not an object or is null`) + for (const key in o.data) { + if (typeof o.data[key] !== 'string') return new Error(`${path}.data['${key}']: is not a string`) + } + + return null +} + export type PaymentState = { amount: number network_fee: number @@ -3018,6 +3142,29 @@ export const UserInfoValidate = (o?: UserInfo, opts: UserInfoOptions = {}, path: return null } +export type UserOffers = { + offers: OfferConfig[] +} +export const UserOffersOptionalFields: [] = [] +export type UserOffersOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + offers_ItemOptions?: OfferConfigOptions + offers_CustomCheck?: (v: OfferConfig[]) => boolean +} +export const UserOffersValidate = (o?: UserOffers, opts: UserOffersOptions = {}, path: string = 'UserOffers::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.offers)) return new Error(`${path}.offers: is not an array`) + for (let index = 0; index < o.offers.length; index++) { + const offersErr = OfferConfigValidate(o.offers[index], opts.offers_ItemOptions, `${path}.offers[${index}]`) + if (offersErr !== null) return offersErr + } + if (opts.offers_CustomCheck && !opts.offers_CustomCheck(o.offers)) return new Error(`${path}.offers: custom check failed`) + + return null +} + export type UserOperation = { amount: number confirmed: boolean diff --git a/proto/service/methods.proto b/proto/service/methods.proto index 36f9089e..a372331d 100644 --- a/proto/service/methods.proto +++ b/proto/service/methods.proto @@ -467,6 +467,42 @@ service LightningPub { option (http_route) = "/api/user/lnurl_channel/url"; option (nostr) = true; } + + rpc GetUserOffers(structs.Empty) returns (structs.UserOffers){ + option (auth_type) = "User"; + option (http_method) = "get"; + option (http_route) = "/api/user/offers/get"; + option (nostr) = true; + } + + rpc GetUserOffer(structs.OfferId) returns (structs.OfferConfig){ + option (auth_type) = "User"; + option (http_method) = "get"; + option (http_route) = "/api/user/offer/get"; + option (nostr) = true; + } + + rpc UpdateUserOffer(structs.OfferConfig) returns (structs.Empty){ + option (auth_type) = "User"; + option (http_method) = "post"; + option (http_route) = "/api/user/offer/update"; + option (nostr) = true; + } + + rpc DeleteUserOffer(structs.OfferId) returns (structs.Empty){ + option (auth_type) = "User"; + option (http_method) = "post"; + option (http_route) = "/api/user/offer/delete"; + option (nostr) = true; + } + + rpc AddUserOffer(structs.OfferConfig) returns (structs.OfferId){ + option (auth_type) = "User"; + option (http_method) = "post"; + option (http_route) = "/api/user/offer/add"; + option (nostr) = true; + } + rpc GetDebitAuthorizations(structs.Empty) returns (structs.DebitAuthorizations){ option (auth_type) = "User"; option (http_method) = "get"; diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 79e43b44..deebabe4 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -269,6 +269,8 @@ message AddAppUserInvoiceRequest { string payer_identifier = 2; string http_callback_url = 3; NewInvoiceRequest invoice_req = 4; + optional PayerData payer_data = 5; + optional string offer_string = 6; } message GetAppUserRequest { @@ -331,6 +333,10 @@ message PayAddressResponse{ int64 network_fee = 4; } +message PayerData { + map data = 1; +} + message NewInvoiceRequest{ int64 amountSats = 1; string memo = 2; @@ -613,4 +619,25 @@ message DebitResponse { Empty denied = 3; string invoice = 4; } +} + +enum OfferDataType { + DATA_STRING = 0; +} + +message OfferId { + string offer_id = 1; +} + +message OfferConfig { + string offer_id = 1; + string label = 2; + int64 price_sats = 3; + string callback_url = 4; + map expected_data = 5; + string noffer = 6; +} + +message UserOffers { + repeated OfferConfig offers = 1; } \ No newline at end of file diff --git a/src/nostrMiddleware.ts b/src/nostrMiddleware.ts index 19a138af..541bb772 100644 --- a/src/nostrMiddleware.ts +++ b/src/nostrMiddleware.ts @@ -49,7 +49,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett } if (event.kind === 21001) { const offerReq = j as NofferData - mainHandler.handleNip69Noffer(offerReq, event) + mainHandler.offerManager.handleNip69Noffer(offerReq, event) return } else if (event.kind === 21002) { const debitReq = j as NdebitData diff --git a/src/services/main/applicationManager.ts b/src/services/main/applicationManager.ts index 627bb149..a271e04b 100644 --- a/src/services/main/applicationManager.ts +++ b/src/services/main/applicationManager.ts @@ -193,7 +193,10 @@ export default class { if (req.invoice_req.zap) { zapInfo = this.paymentManager.validateZapEvent(req.invoice_req.zap, req.invoice_req.amountSats) } - const opts: InboundOptionals = { callbackUrl: cbUrl, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app, zapInfo } + const opts: InboundOptionals = { + callbackUrl: cbUrl, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app, zapInfo, + offerId: req.offer_string, payerData: req.payer_data?.data + } const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts) return { invoice: appUserInvoice.invoice diff --git a/src/services/main/index.ts b/src/services/main/index.ts index bd9de9b9..45ef3d8f 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -24,6 +24,7 @@ import { Unlocker } from "./unlocker.js" import { defaultInvoiceExpiry } from "../storage/paymentStorage.js" import { DebitManager } from "./debitManager.js" import { NofferData } from "nostr-tools/lib/types/nip69.js" +import { OfferManager } from "./offerManager.js" type UserOperationsSub = { id: string @@ -49,6 +50,7 @@ export default class { liquidityManager: LiquidityManager liquidityProvider: LiquidityProvider debitManager: DebitManager + offerManager: OfferManager utils: Utils rugPullTracker: RugPullTracker unlocker: Unlocker @@ -71,6 +73,8 @@ export default class { this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager) this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager) this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager) + this.offerManager = new OfferManager(this.storage, this.lnd, this.applicationManager, this.productManager) + } Stop() { @@ -89,6 +93,7 @@ export default class { this.nostrSend = f this.liquidityProvider.attachNostrSend(f) this.debitManager.attachNostrSend(f) + this.offerManager.attachNostrSend(f) } htlcCb: HtlcCb = (e) => { @@ -286,70 +291,6 @@ export default class { log({ unsigned: event }) this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined) } - - async getNofferInvoice(offerReq: NofferData, appId: string): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> { - try { - - const { remote } = await this.lnd.ChannelBalance() - const { offer, amount } = offerReq - const split = offer.split(':') - if (split.length === 1) { - if (!amount || isNaN(amount) || amount < 10 || amount > remote) { - return { success: false, code: 5, max: remote } - } - const res = await this.applicationManager.AddAppUserInvoice(appId, { - http_callback_url: "", payer_identifier: split[0], receiver_identifier: split[0], - invoice_req: { amountSats: amount, memo: "Default NIP-69 Offer", zap: offerReq.zap } - }) - return { success: true, invoice: res.invoice } - } else if (split[0] === 'p') { - const product = await this.productManager.NewProductInvoice(split[1]) - return { success: true, invoice: product.invoice } - } else { - return { success: false, code: 1, max: remote } - } - } catch (e: any) { - getLogger({ component: "noffer" })(ERROR, e.message || e) - return { success: false, code: 1, max: 0 } - } - } - - async handleNip69Noffer(offerReq: NofferData, event: NostrEvent) { - const offerInvoice = await this.getNofferInvoice(offerReq, event.appId) - if (!offerInvoice.success) { - const code = offerInvoice.code - const e = newNofferResponse(JSON.stringify({ code, error: codeToMessage(code), range: { min: 10, max: offerInvoice.max } }), event) - this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } }) - return - } - const e = newNofferResponse(JSON.stringify({ bolt11: offerInvoice.invoice }), event) - this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } }) - return - } -} - -const codeToMessage = (code: number) => { - switch (code) { - case 1: return 'Invalid Offer' - case 2: return 'Temporary Failure' - case 3: return 'Expired Offer' - case 4: return 'Unsupported Feature' - case 5: return 'Invalid Amount' - default: throw new Error("unknown error code" + code) - } -} - -const newNofferResponse = (content: string, event: NostrEvent): UnsignedEvent => { - return { - content, - created_at: Math.floor(Date.now() / 1000), - kind: 21001, - pubkey: "", - tags: [ - ['p', event.pub], - ['e', event.id], - ], - } } diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts new file mode 100644 index 00000000..0a1fea96 --- /dev/null +++ b/src/services/main/offerManager.ts @@ -0,0 +1,239 @@ +import crypto from 'crypto'; +import * as Types from "../../../proto/autogenerated/ts/types.js"; +import ApplicationManager from "./applicationManager.js"; +import ProductManager from "./productManager.js"; +import Storage from '../storage/index.js' +import LND from "../lnd/lnd.js" +import { ERROR, getLogger } from "../helpers/logger.js"; +import { DebitAccess, DebitAccessRules } from '../storage/entity/DebitAccess.js'; +import paymentManager from './paymentManager.js'; +import { Application } from '../storage/entity/Application.js'; +import { ApplicationUser } from '../storage/entity/ApplicationUser.js'; +import { NostrEvent, NostrSend, SendData, SendInitiator } from '../nostr/handler.js'; +import { UnsignedEvent } from 'nostr-tools'; +import { BudgetFrequency, NdebitData, NdebitFailure, NdebitSuccess, NdebitSuccessPayment, RecurringDebitTimeUnit } from 'nostr-tools/lib/types/nip68.js'; +import { NofferData } from "nostr-tools/lib/types/nip69.js" +import { UserOffer } from '../storage/entity/UserOffer.js'; +import { DeepPartial } from 'typeorm'; +import { nip19 } from 'nostr-tools'; +import { LoadNosrtSettingsFromEnv } from '../nostr/index.js'; + +const mapToOfferConfig = (offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => { + if (offer.expected_data) { + const keys = Object.keys(offer.expected_data) + for (const key of keys) { + const v = offer.expected_data[key] as Types.OfferDataType + if (!Types.OfferDataType[v]) { + offer.expected_data[key] = Types.OfferDataType.DATA_STRING + } + } + } + const offerStr = offer.offer_id + const priceType: nip19.OfferPriceType = offer.price_sats === 0 ? nip19.OfferPriceType.Spontaneous : nip19.OfferPriceType.Fixed + const noffer = nip19.nofferEncode({ pubkey, offer: offerStr, priceType, relay }) + return { + label: offer.label, + price_sats: offer.price_sats, + callback_url: offer.callback_url, + expected_data: (offer.expected_data || {}) as Record, + offer_id: offer.offer_id, + noffer: noffer + } +} +export class OfferManager { + + + + + + + + _nostrSend: NostrSend | null = null + + applicationManager: ApplicationManager + productManager: ProductManager + storage: Storage + lnd: LND + logger = getLogger({ component: 'DebitManager' }) + constructor(storage: Storage, lnd: LND, applicationManager: ApplicationManager, productManager: ProductManager) { + this.storage = storage + this.lnd = lnd + this.applicationManager = applicationManager + this.productManager = productManager + } + + attachNostrSend = (nostrSend: NostrSend) => { + this._nostrSend = nostrSend + } + nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => { + if (!this._nostrSend) { + throw new Error("No nostrSend attached") + } + this._nostrSend(initiator, data, relays) + } + + async AddUserOffer(ctx: Types.UserContext, req: Types.OfferConfig): Promise { + const newOffer = await this.storage.offerStorage.AddUserOffer(ctx.app_user_id, { + expected_data: req.expected_data, + label: req.label, + price_sats: req.price_sats, + callback_url: req.callback_url, + }) + return { + offer_id: newOffer.offer_id + } + } + + async DeleteUserOffer(ctx: Types.UserContext, req: Types.OfferId) { + await this.storage.offerStorage.DeleteUserOffer(ctx.app_user_id, req.offer_id) + } + + async UpdateUserOffer(ctx: Types.UserContext, req: Types.OfferConfig) { + await this.storage.offerStorage.UpdateUserOffer(ctx.app_user_id, { + expected_data: req.expected_data, + label: req.label, + price_sats: req.price_sats, + callback_url: req.callback_url, + }) + } + + async GetUserOffer(ctx: Types.UserContext, req: Types.OfferId): Promise { + const app = await this.applicationManager.GetApp(ctx.app_id) + if (!app) { + throw new Error("App not found") + } + const offer = await this.storage.offerStorage.GetUserOffer(ctx.app_user_id, req.offer_id) + if (!offer) { + throw new Error("Offer not found") + } + const nostrSettings = LoadNosrtSettingsFromEnv() + return mapToOfferConfig(offer, { pubkey: app.npub, relay: nostrSettings.relays[0] }) + } + + async GetUserOffers(ctx: Types.UserContext): Promise { + const app = await this.applicationManager.GetApp(ctx.app_id) + if (!app) { + throw new Error("App not found") + } + const offers = await this.storage.offerStorage.GetUserOffers(ctx.app_user_id) + const nostrSettings = LoadNosrtSettingsFromEnv() + return { + offers: offers.map(o => mapToOfferConfig(o, { pubkey: app.npub, relay: nostrSettings.relays[0] })) + } + } + + ValidateExpectedData(userOffer: UserOffer, payerData: any): { passed: false, validated: undefined } | { passed: true, validated: Record } { + const expected = userOffer.expected_data + if (!expected) { + return { passed: true, validated: {} } + } + const expectedKeys = Object.keys(expected) + if (expectedKeys.length === 0) { + return { passed: true, validated: {} } + } + if (typeof payerData !== 'object' || payerData === null) { + return { passed: false, validated: undefined } + } + const validated: Record = {} + for (const key of expectedKeys) { + if (typeof payerData[key] !== 'string') { + return { passed: false, validated: undefined } + } + validated[key] = payerData[key] + } + return { passed: true, validated } + } + + async handleNip69Noffer(offerReq: NofferData, event: NostrEvent) { + const offerInvoice = await this.getNofferInvoice(offerReq, event.appId) + if (!offerInvoice.success) { + const code = offerInvoice.code + const e = newNofferResponse(JSON.stringify({ code, error: codeToMessage(code), range: { min: 10, max: offerInvoice.max } }), event) + this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } }) + return + } + const e = newNofferResponse(JSON.stringify({ bolt11: offerInvoice.invoice }), event) + this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } }) + return + } + + async HandleDefaultUserOffer(offerReq: NofferData, appId: string, remote: number): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> { + const { amount, offer } = offerReq + if (!amount || isNaN(amount) || amount < 10 || amount > remote) { + return { success: false, code: 5, max: remote } + } + const res = await this.applicationManager.AddAppUserInvoice(appId, { + http_callback_url: "", payer_identifier: offer, receiver_identifier: offer, + invoice_req: { amountSats: amount, memo: "Default NIP-69 Offer", zap: offerReq.zap }, + offer_string: 'offer' + }) + return { success: true, invoice: res.invoice } + } + + async HandleUserOffer(offerReq: NofferData, appId: string, remote: number): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> { + const { amount, offer } = offerReq + const userOffer = await this.storage.offerStorage.GetOffer(offer) + if (!userOffer) { + return this.HandleDefaultUserOffer(offerReq, appId, remote) + } + let amt = userOffer.price_sats + if (userOffer.price_sats === 0) { + if (!amount || isNaN(amount) || amount < 10 || amount > remote) { + return { success: false, code: 5, max: remote } + } + amt = amount + } + const { passed, validated } = this.ValidateExpectedData(userOffer, offerReq.payer_data) + if (!passed) { + return { success: false, code: 1, max: remote } + } + const res = await this.applicationManager.AddAppUserInvoice(appId, { + http_callback_url: userOffer.callback_url, payer_identifier: offer, receiver_identifier: offer, + invoice_req: { amountSats: amt, memo: userOffer.label, zap: offerReq.zap }, + payer_data: validated ? { data: validated } : undefined, + offer_string: offer + }) + return { success: true, invoice: res.invoice } + } + + async getNofferInvoice(offerReq: NofferData, appId: string): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> { + try { + const { remote } = await this.lnd.ChannelBalance() + const split = offerReq.offer.split(':') + if (split.length === 1) { + return this.HandleUserOffer(offerReq, appId, remote) + } else if (split[0] === 'p') { + const product = await this.productManager.NewProductInvoice(split[1]) + return { success: true, invoice: product.invoice } + } else { + return { success: false, code: 1, max: remote } + } + } catch (e: any) { + getLogger({ component: "noffer" })(ERROR, e.message || e) + return { success: false, code: 1, max: 0 } + } + } + +} +const newNofferResponse = (content: string, event: NostrEvent): UnsignedEvent => { + return { + content, + created_at: Math.floor(Date.now() / 1000), + kind: 21001, + pubkey: "", + tags: [ + ['p', event.pub], + ['e', event.id], + ], + } +} +const codeToMessage = (code: number) => { + switch (code) { + case 1: return 'Invalid Offer' + case 2: return 'Temporary Failure' + case 3: return 'Expired Offer' + case 4: return 'Unsupported Feature' + case 5: return 'Invalid Amount' + default: throw new Error("unknown error code" + code) + } +} \ No newline at end of file diff --git a/src/services/serverMethods/index.ts b/src/services/serverMethods/index.ts index a998d2ef..7a9b0e0c 100644 --- a/src/services/serverMethods/index.ts +++ b/src/services/serverMethods/index.ts @@ -323,6 +323,33 @@ export default (mainHandler: Main): Types.ServerMethods => { }, RespondToDebit: async ({ ctx, req }) => { return mainHandler.debitManager.RespondToDebit(ctx, req); + }, + AddUserOffer: async ({ ctx, req }) => { + const err = Types.OfferConfigValidate(req, { + label_CustomCheck: label => label !== '', + }) + if (err != null) throw new Error(err.message) + return mainHandler.offerManager.AddUserOffer(ctx, req) + }, + DeleteUserOffer: async ({ ctx, req }) => { + const err = Types.OfferIdValidate(req, { + offer_id_CustomCheck: id => id !== '' + }) + if (err != null) throw new Error(err.message) + return mainHandler.offerManager.DeleteUserOffer(ctx, req) + }, + UpdateUserOffer: async ({ ctx, req }) => { + return mainHandler.offerManager.UpdateUserOffer(ctx, req) + }, + GetUserOffers: async ({ ctx }) => { + return mainHandler.offerManager.GetUserOffers(ctx) + }, + GetUserOffer: async ({ ctx, req }) => { + const err = Types.OfferIdValidate(req, { + offer_id_CustomCheck: id => id !== '' + }) + if (err != null) throw new Error(err.message) + return mainHandler.offerManager.GetUserOffer(ctx, req) } } } \ No newline at end of file diff --git a/src/services/storage/entity/UserOffer.ts b/src/services/storage/entity/UserOffer.ts new file mode 100644 index 00000000..409c4e8d --- /dev/null +++ b/src/services/storage/entity/UserOffer.ts @@ -0,0 +1,37 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from "typeorm" +import { User } from "./User.js" + +@Entity() +export class UserOffer { + + @PrimaryGeneratedColumn() + serial_id: number + + @Column() + app_user_id: string + + @Column({ unique: true, nullable: false }) + offer_id: string + + @Column() + label: string + + @Column({ default: 0 }) + price_sats: number + + @Column({ default: "" }) + callback_url: string + + @Column({ + nullable: true, + type: 'simple-json', + default: null + }) + expected_data: Record | null + + @CreateDateColumn() + created_at: Date + + @UpdateDateColumn() + updated_at: Date +} diff --git a/src/services/storage/entity/UserReceivingInvoice.ts b/src/services/storage/entity/UserReceivingInvoice.ts index 356f4886..cef021bc 100644 --- a/src/services/storage/entity/UserReceivingInvoice.ts +++ b/src/services/storage/entity/UserReceivingInvoice.ts @@ -58,6 +58,15 @@ export class UserReceivingInvoice { }) zap_info?: ZapInfo + @Column({ + nullable: true, + type: 'simple-json' + }) + payer_data?: Record + + @Column({ default: "" }) + offer_id?: string + @Column({ nullable: true, }) diff --git a/src/services/storage/index.ts b/src/services/storage/index.ts index a37b1de1..f266fd01 100644 --- a/src/services/storage/index.ts +++ b/src/services/storage/index.ts @@ -11,6 +11,7 @@ import EventsLogManager from "./eventsLog.js"; import { LiquidityStorage } from "./liquidityStorage.js"; import { StateBundler } from "./stateBundler.js"; import DebitStorage from "./debitStorage.js" +import OfferStorage from "./offerStorage.js" export type StorageSettings = { dbSettings: DbSettings eventLogPath: string @@ -30,6 +31,7 @@ export default class { metricsStorage: MetricsStorage liquidityStorage: LiquidityStorage debitStorage: DebitStorage + offerStorage: OfferStorage eventsLog: EventsLogManager stateBundler: StateBundler constructor(settings: StorageSettings) { @@ -47,6 +49,7 @@ export default class { this.metricsStorage = new MetricsStorage(this.settings) this.liquidityStorage = new LiquidityStorage(this.DB, this.txQueue) this.debitStorage = new DebitStorage(this.DB, this.txQueue) + this.offerStorage = new OfferStorage(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 }; diff --git a/src/services/storage/offerStorage.ts b/src/services/storage/offerStorage.ts new file mode 100644 index 00000000..f1941f86 --- /dev/null +++ b/src/services/storage/offerStorage.ts @@ -0,0 +1,41 @@ +import { DataSource, EntityManager } from "typeorm" +import crypto from 'crypto'; +import UserStorage from './userStorage.js'; +import TransactionsQueue from "./transactionsQueue.js"; +import { DebitAccess, DebitAccessRules } from "./entity/DebitAccess.js"; +import { UserOffer } from "./entity/UserOffer.js"; +export default class { + + + DB: DataSource | EntityManager + txQueue: TransactionsQueue + constructor(DB: DataSource | EntityManager, txQueue: TransactionsQueue) { + this.DB = DB + this.txQueue = txQueue + } + async AddUserOffer(appUserId: string, req: Partial): Promise { + const newUserOffer = this.DB.getRepository(UserOffer).create({ + ...req, + app_user_id: appUserId, + offer_id: crypto.randomBytes(34).toString('hex') + }) + return this.txQueue.PushToQueue({ exec: async db => db.getRepository(UserOffer).save(newUserOffer), dbTx: false, description: `add offer for ${appUserId}: ${req.label} ` }) + } + + async DeleteUserOffer(appUserId: string, offerId: string, entityManager = this.DB) { + await entityManager.getRepository(UserOffer).delete({ app_user_id: appUserId, offer_id: offerId }) + } + async UpdateUserOffer(app_user_id: string, req: Partial) { + return this.DB.getRepository(UserOffer).update({ app_user_id, offer_id: req.offer_id }, req) + } + + async GetUserOffers(app_user_id: string): Promise { + return this.DB.getRepository(UserOffer).find({ where: { app_user_id } }) + } + async GetUserOffer(app_user_id: string, offer_id: string): Promise { + return this.DB.getRepository(UserOffer).findOne({ where: { app_user_id, offer_id } }) + } + async GetOffer(offer_id: string): Promise { + return this.DB.getRepository(UserOffer).findOne({ where: { offer_id } }) + } +} \ No newline at end of file diff --git a/src/services/storage/paymentStorage.ts b/src/services/storage/paymentStorage.ts index 06e3043a..051b9943 100644 --- a/src/services/storage/paymentStorage.ts +++ b/src/services/storage/paymentStorage.ts @@ -13,7 +13,7 @@ import { UserToUserPayment } from './entity/UserToUserPayment.js'; import { Application } from './entity/Application.js'; import TransactionsQueue from "./transactionsQueue.js"; import { LoggedEvent } from './eventsLog.js'; -export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application, zapInfo?: ZapInfo } +export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application, zapInfo?: ZapInfo, offerId?: string, payerData?: Record } export const defaultInvoiceExpiry = 60 * 60 export default class { DB: DataSource | EntityManager @@ -102,7 +102,9 @@ export default class { payer: options.expectedPayer, linkedApplication: options.linkedApplication, zap_info: options.zapInfo, - liquidityProvider: providerDestination + liquidityProvider: providerDestination, + offer_id: options.offerId, + payer_data: options.payerData, }) return this.txQueue.PushToQueue({ exec: async db => db.getRepository(UserReceivingInvoice).save(newUserInvoice), dbTx: false, description: `add invoice for ${user.user_id} linked to ${options.linkedApplication?.app_id}: ${invoice} ` }) } From eba8ee75e04a89ca6f8201ac34ad6fabaff58c50 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 16:08:12 +0000 Subject: [PATCH 02/35] default offer flag --- proto/autogenerated/client.md | 1 + proto/autogenerated/go/types.go | 1 + proto/autogenerated/ts/types.ts | 5 +++++ proto/service/structs.proto | 1 + src/services/main/offerManager.ts | 9 +++++---- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 4c5b223f..b680cdbf 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -1120,6 +1120,7 @@ The nostr server will send back a message response, and inside the body there wi ### OfferConfig - __callback_url__: _string_ + - __default_offer__: _boolean_ - __expected_data__: MAP with key: _string_ and value: _[OfferDataType](#OfferDataType)_ - __label__: _string_ - __noffer__: _string_ diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 1881e0be..27e77fd5 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -396,6 +396,7 @@ type NewInvoiceResponse struct { } type OfferConfig struct { Callback_url string `json:"callback_url"` + Default_offer bool `json:"default_offer"` Expected_data map[string]OfferDataType `json:"expected_data"` Label string `json:"label"` Noffer string `json:"noffer"` diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index 814ded3a..ef7d35cf 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -2244,6 +2244,7 @@ export const NewInvoiceResponseValidate = (o?: NewInvoiceResponse, opts: NewInvo export type OfferConfig = { callback_url: string + default_offer: boolean expected_data: Record label: string noffer: string @@ -2254,6 +2255,7 @@ export const OfferConfigOptionalFields: [] = [] export type OfferConfigOptions = OptionsBaseMessage & { checkOptionalsAreSet?: [] callback_url_CustomCheck?: (v: string) => boolean + default_offer_CustomCheck?: (v: boolean) => boolean expected_data_CustomCheck?: (v: Record) => boolean label_CustomCheck?: (v: string) => boolean noffer_CustomCheck?: (v: string) => boolean @@ -2267,6 +2269,9 @@ export const OfferConfigValidate = (o?: OfferConfig, opts: OfferConfigOptions = if (typeof o.callback_url !== 'string') return new Error(`${path}.callback_url: is not a string`) if (opts.callback_url_CustomCheck && !opts.callback_url_CustomCheck(o.callback_url)) return new Error(`${path}.callback_url: custom check failed`) + if (typeof o.default_offer !== 'boolean') return new Error(`${path}.default_offer: is not a boolean`) + if (opts.default_offer_CustomCheck && !opts.default_offer_CustomCheck(o.default_offer)) return new Error(`${path}.default_offer: custom check failed`) + if (typeof o.expected_data !== 'object' || o.expected_data === null) return new Error(`${path}.expected_data: is not an object or is null`) for (const key in o.expected_data) { if (!enumCheckOfferDataType(o.expected_data[key])) return new Error(`${path}.expected_data['${key}']: is not a OfferDataType`) diff --git a/proto/service/structs.proto b/proto/service/structs.proto index deebabe4..955b113a 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -636,6 +636,7 @@ message OfferConfig { string callback_url = 4; map expected_data = 5; string noffer = 6; + bool default_offer = 7; } message UserOffers { diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts index 0a1fea96..52d0422d 100644 --- a/src/services/main/offerManager.ts +++ b/src/services/main/offerManager.ts @@ -18,7 +18,7 @@ import { DeepPartial } from 'typeorm'; import { nip19 } from 'nostr-tools'; import { LoadNosrtSettingsFromEnv } from '../nostr/index.js'; -const mapToOfferConfig = (offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => { +const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => { if (offer.expected_data) { const keys = Object.keys(offer.expected_data) for (const key of keys) { @@ -37,7 +37,8 @@ const mapToOfferConfig = (offer: UserOffer, { pubkey, relay }: { pubkey: string, callback_url: offer.callback_url, expected_data: (offer.expected_data || {}) as Record, offer_id: offer.offer_id, - noffer: noffer + noffer: noffer, + default_offer: appUserId === offer.app_user_id } } export class OfferManager { @@ -107,7 +108,7 @@ export class OfferManager { throw new Error("Offer not found") } const nostrSettings = LoadNosrtSettingsFromEnv() - return mapToOfferConfig(offer, { pubkey: app.npub, relay: nostrSettings.relays[0] }) + return mapToOfferConfig(ctx.app_user_id, offer, { pubkey: app.npub, relay: nostrSettings.relays[0] }) } async GetUserOffers(ctx: Types.UserContext): Promise { @@ -118,7 +119,7 @@ export class OfferManager { const offers = await this.storage.offerStorage.GetUserOffers(ctx.app_user_id) const nostrSettings = LoadNosrtSettingsFromEnv() return { - offers: offers.map(o => mapToOfferConfig(o, { pubkey: app.npub, relay: nostrSettings.relays[0] })) + offers: offers.map(o => mapToOfferConfig(ctx.app_user_id, o, { pubkey: app.npub, relay: nostrSettings.relays[0] })) } } From 6e1e444b1c5d03515f73d696136ab016e7d0c384 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 16:17:02 +0000 Subject: [PATCH 03/35] default source logic --- src/services/main/offerManager.ts | 8 ++++++++ src/services/storage/offerStorage.ts | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts index 52d0422d..9c238600 100644 --- a/src/services/main/offerManager.ts +++ b/src/services/main/offerManager.ts @@ -117,6 +117,14 @@ export class OfferManager { throw new Error("App not found") } const offers = await this.storage.offerStorage.GetUserOffers(ctx.app_user_id) + const defaultOffer = offers.find(o => o.app_user_id === o.offer_id) + let toAppend: UserOffer | undefined = undefined + if (!defaultOffer) { + toAppend = await this.storage.offerStorage.AddDefaultUserOffer(ctx.app_user_id) + } + if (toAppend) { + offers.push(toAppend) + } const nostrSettings = LoadNosrtSettingsFromEnv() return { offers: offers.map(o => mapToOfferConfig(ctx.app_user_id, o, { pubkey: app.npub, relay: nostrSettings.relays[0] })) diff --git a/src/services/storage/offerStorage.ts b/src/services/storage/offerStorage.ts index f1941f86..8fa4b532 100644 --- a/src/services/storage/offerStorage.ts +++ b/src/services/storage/offerStorage.ts @@ -13,6 +13,14 @@ export default class { this.DB = DB this.txQueue = txQueue } + async AddDefaultUserOffer(appUserId: string): Promise { + const newUserOffer = this.DB.getRepository(UserOffer).create({ + app_user_id: appUserId, + offer_id: appUserId, + label: 'Default NIP-69 Offer', + }) + return this.txQueue.PushToQueue({ exec: async db => db.getRepository(UserOffer).save(newUserOffer), dbTx: false, description: `add default offer for ${appUserId}` }) + } async AddUserOffer(appUserId: string, req: Partial): Promise { const newUserOffer = this.DB.getRepository(UserOffer).create({ ...req, From fa92c325ddee0088b71b3e4879ec690cb14b0bb7 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 16:33:28 +0000 Subject: [PATCH 04/35] db migrations --- datasource.js | 8 +++--- src/services/storage/db.ts | 3 ++- .../migrations/1733502626042-user_offer.ts | 26 +++++++++++++++++++ src/services/storage/migrations/runner.ts | 3 ++- 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 src/services/storage/migrations/1733502626042-user_offer.ts diff --git a/datasource.js b/datasource.js index 949c21f7..402553a1 100644 --- a/datasource.js +++ b/datasource.js @@ -16,6 +16,7 @@ import { LndNodeInfo } from "./build/src/services/storage/entity/LndNodeInfo.js" import { TrackedProvider } from "./build/src/services/storage/entity/TrackedProvider.js" import { InviteToken } from "./build/src/services/storage/entity/InviteToken.js" import { DebitAccess } from "./build/src/services/storage/entity/DebitAccess.js" +import { UserOffer } from "./build/src/services/storage/entity/UserOffer.js" import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js' import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js' @@ -27,13 +28,14 @@ import { DebitAccess1726496225078 } from './build/src/services/storage/migration import { DebitAccessFixes1726685229264 } from './build/src/services/storage/migrations/1726685229264-debit_access_fixes.js' import { DebitToPub1727105758354 } from './build/src/services/storage/migrations/1727105758354-debit_to_pub.js' import { UserCbUrl1727112281043 } from './build/src/services/storage/migrations/1727112281043-user_cb_url.js' +import { UserOffer1733502626042 } from './build/src/services/storage/migrations/1733502626042-user_offer.js' export default new DataSource({ type: "sqlite", database: "db.sqlite", // logging: true, - migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043], + migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042], entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, - UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess], + UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess, UserOffer], // synchronize: true, }) -//npx typeorm migration:generate ./src/services/storage/migrations/usert_cb_url -d ./datasource.js \ No newline at end of file +//npx typeorm migration:generate ./src/services/storage/migrations/user_offer -d ./datasource.js \ No newline at end of file diff --git a/src/services/storage/db.ts b/src/services/storage/db.ts index 6c580442..d3af00a6 100644 --- a/src/services/storage/db.ts +++ b/src/services/storage/db.ts @@ -23,6 +23,7 @@ import { TrackedProvider } from "./entity/TrackedProvider.js" import { InviteToken } from "./entity/InviteToken.js" import { DebitAccess } from "./entity/DebitAccess.js" import { RootOperation } from "./entity/RootOperation.js" +import { UserOffer } from "./entity/UserOffer.js" export type DbSettings = { @@ -63,7 +64,7 @@ export default async (settings: DbSettings, migrations: Function[]): Promise<{ s // logging: true, entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, - InviteToken, DebitAccess + InviteToken, DebitAccess, UserOffer ], //synchronize: true, migrations diff --git a/src/services/storage/migrations/1733502626042-user_offer.ts b/src/services/storage/migrations/1733502626042-user_offer.ts new file mode 100644 index 00000000..81e321fe --- /dev/null +++ b/src/services/storage/migrations/1733502626042-user_offer.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UserOffer1733502626042 implements MigrationInterface { + name = 'UserOffer1733502626042' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "user_offer" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "offer_id" varchar NOT NULL, "label" varchar NOT NULL, "price_sats" integer NOT NULL DEFAULT (0), "callback_url" varchar NOT NULL DEFAULT (''), "expected_data" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_478f72095abd8a516d3a309a5c5" UNIQUE ("offer_id"))`); + await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`); + await queryRunner.query(`CREATE TABLE "temporary_user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, "payer_data" text, "offer_id" varchar NOT NULL DEFAULT (''), CONSTRAINT "FK_2c0dfb3483f3e5e7e3cdd5dc71f" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5263bde2a519db9ea608b702ec8" FOREIGN KEY ("productProductId") REFERENCES "product" ("product_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d4bb1e4c60e8a869f1f43ca2e31" FOREIGN KEY ("payerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_714a8b7d4f89f8a802ca181b789" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider" FROM "user_receiving_invoice"`); + await queryRunner.query(`DROP TABLE "user_receiving_invoice"`); + await queryRunner.query(`ALTER TABLE "temporary_user_receiving_invoice" RENAME TO "user_receiving_invoice"`); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`); + await queryRunner.query(`ALTER TABLE "user_receiving_invoice" RENAME TO "temporary_user_receiving_invoice"`); + await queryRunner.query(`CREATE TABLE "user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, CONSTRAINT "FK_2c0dfb3483f3e5e7e3cdd5dc71f" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5263bde2a519db9ea608b702ec8" FOREIGN KEY ("productProductId") REFERENCES "product" ("product_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d4bb1e4c60e8a869f1f43ca2e31" FOREIGN KEY ("payerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_714a8b7d4f89f8a802ca181b789" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider" FROM "temporary_user_receiving_invoice"`); + await queryRunner.query(`DROP TABLE "temporary_user_receiving_invoice"`); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `); + await queryRunner.query(`DROP TABLE "user_offer"`); + } + +} diff --git a/src/services/storage/migrations/runner.ts b/src/services/storage/migrations/runner.ts index 767e3d2e..70e5d905 100644 --- a/src/services/storage/migrations/runner.ts +++ b/src/services/storage/migrations/runner.ts @@ -17,7 +17,8 @@ import { DebitAccessFixes1726685229264 } from './1726685229264-debit_access_fixe import { DebitToPub1727105758354 } from './1727105758354-debit_to_pub.js' import { UserCbUrl1727112281043 } from './1727112281043-user_cb_url.js' import { RootOps1732566440447 } from './1732566440447-root_ops.js' -const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043] +import { UserOffer1733502626042 } from './1733502626042-user_offer.js' +const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042] const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447] export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise => { if (arg === 'fake_initial_migration') { From 9dd77fe0bfebc289e1b3eefe41d8e57f39138944 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 16:53:34 +0000 Subject: [PATCH 05/35] http method fix --- proto/autogenerated/client.md | 2 +- proto/autogenerated/go/http_client.go | 9 ++++++++- proto/autogenerated/ts/express_server.ts | 2 +- proto/autogenerated/ts/http_client.ts | 2 +- proto/autogenerated/ts/nostr_client.ts | 1 + proto/service/methods.proto | 2 +- 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index b680cdbf..12ec7d3c 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -580,7 +580,7 @@ The nostr server will send back a message response, and inside the body there wi - GetUserOffer - auth type: __User__ - - http method: __get__ + - http method: __post__ - http route: __/api/user/offer/get__ - input: [OfferId](#OfferId) - output: [OfferConfig](#OfferConfig) diff --git a/proto/autogenerated/go/http_client.go b/proto/autogenerated/go/http_client.go index 0289dfd3..a5a0806f 100644 --- a/proto/autogenerated/go/http_client.go +++ b/proto/autogenerated/go/http_client.go @@ -1087,7 +1087,14 @@ func NewClient(params ClientParams) *Client { return nil, err } finalRoute := "/api/user/offer/get" - resBody, err := doGetRequest(params.BaseURL+finalRoute, auth) + body, err := json.Marshal(req) + if err != nil { + return nil, err + } + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return nil, err + } result := ResultError{} err = json.Unmarshal(resBody, &result) if err != nil { diff --git a/proto/autogenerated/ts/express_server.ts b/proto/autogenerated/ts/express_server.ts index 603d93d5..3c418d2d 100644 --- a/proto/autogenerated/ts/express_server.ts +++ b/proto/autogenerated/ts/express_server.ts @@ -1114,7 +1114,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { } 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.GetUserOffer) throw new Error('method: GetUserOffer is not implemented') - app.get('/api/user/offer/get', async (req, res) => { + app.post('/api/user/offer/get', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'GetUserOffer', 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 = {} diff --git a/proto/autogenerated/ts/http_client.ts b/proto/autogenerated/ts/http_client.ts index b2ed71f6..5ca384df 100644 --- a/proto/autogenerated/ts/http_client.ts +++ b/proto/autogenerated/ts/http_client.ts @@ -512,7 +512,7 @@ export default (params: ClientParams) => ({ const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offer/get' - const { data } = await axios.get(params.baseUrl + finalRoute, { headers: { 'authorization': auth } }) + 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') { const result = data diff --git a/proto/autogenerated/ts/nostr_client.ts b/proto/autogenerated/ts/nostr_client.ts index 94dd8852..5cf127d2 100644 --- a/proto/autogenerated/ts/nostr_client.ts +++ b/proto/autogenerated/ts/nostr_client.ts @@ -440,6 +440,7 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ 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:'GetUserOffer',authIdentifier:auth, ...nostrRequest }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data if (data.status === 'OK') { diff --git a/proto/service/methods.proto b/proto/service/methods.proto index a372331d..b5a91007 100644 --- a/proto/service/methods.proto +++ b/proto/service/methods.proto @@ -477,7 +477,7 @@ service LightningPub { rpc GetUserOffer(structs.OfferId) returns (structs.OfferConfig){ option (auth_type) = "User"; - option (http_method) = "get"; + option (http_method) = "post"; option (http_route) = "/api/user/offer/get"; option (nostr) = true; } From 72b073d9820f817e104faaf221f9fb66e5c0b59e Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 17:43:08 +0000 Subject: [PATCH 06/35] fix offer id --- src/services/main/offerManager.ts | 2 +- src/services/storage/offerStorage.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts index 9c238600..cc72ce3e 100644 --- a/src/services/main/offerManager.ts +++ b/src/services/main/offerManager.ts @@ -90,7 +90,7 @@ export class OfferManager { } async UpdateUserOffer(ctx: Types.UserContext, req: Types.OfferConfig) { - await this.storage.offerStorage.UpdateUserOffer(ctx.app_user_id, { + await this.storage.offerStorage.UpdateUserOffer(ctx.app_user_id, req.offer_id, { expected_data: req.expected_data, label: req.label, price_sats: req.price_sats, diff --git a/src/services/storage/offerStorage.ts b/src/services/storage/offerStorage.ts index 8fa4b532..41af3ca6 100644 --- a/src/services/storage/offerStorage.ts +++ b/src/services/storage/offerStorage.ts @@ -33,8 +33,8 @@ export default class { async DeleteUserOffer(appUserId: string, offerId: string, entityManager = this.DB) { await entityManager.getRepository(UserOffer).delete({ app_user_id: appUserId, offer_id: offerId }) } - async UpdateUserOffer(app_user_id: string, req: Partial) { - return this.DB.getRepository(UserOffer).update({ app_user_id, offer_id: req.offer_id }, req) + async UpdateUserOffer(app_user_id: string, offerId: string, req: Partial) { + return this.DB.getRepository(UserOffer).update({ app_user_id, offer_id: offerId }, req) } async GetUserOffers(app_user_id: string): Promise { From 4415506ff6cb12eedd64bbababef5c6b2192b418 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 18:02:32 +0000 Subject: [PATCH 07/35] fix uid --- src/services/main/offerManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts index cc72ce3e..d3213183 100644 --- a/src/services/main/offerManager.ts +++ b/src/services/main/offerManager.ts @@ -197,7 +197,7 @@ export class OfferManager { return { success: false, code: 1, max: remote } } const res = await this.applicationManager.AddAppUserInvoice(appId, { - http_callback_url: userOffer.callback_url, payer_identifier: offer, receiver_identifier: offer, + http_callback_url: userOffer.callback_url, payer_identifier: userOffer.app_user_id, receiver_identifier: userOffer.app_user_id, invoice_req: { amountSats: amt, memo: userOffer.label, zap: offerReq.zap }, payer_data: validated ? { data: validated } : undefined, offer_string: offer From da230dfb0e0249239937d04a621bfcb2e743f830 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 18:39:21 +0000 Subject: [PATCH 08/35] deb --- src/services/main/paymentManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 4c120f18..09ce4240 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -576,6 +576,7 @@ export default class { const relays = this.parseTags("relays", nostrEvent.tags, { required: true, multiples: true }) const amount = this.parseTags("amount", nostrEvent.tags) if (+amount !== amt) { + console.log({ amount, amt }) throw new Error("amount mismatch") } return { pub: p[0], eventId: e.length > 0 ? e[0] : "", relays, description: event } From b950dd8bbd6e7ddae1e9009483dadd22bb119045 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 19:04:46 +0000 Subject: [PATCH 09/35] fix noffer --- src/services/main/offerManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts index d3213183..fc31d441 100644 --- a/src/services/main/offerManager.ts +++ b/src/services/main/offerManager.ts @@ -30,7 +30,7 @@ const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay } } const offerStr = offer.offer_id const priceType: nip19.OfferPriceType = offer.price_sats === 0 ? nip19.OfferPriceType.Spontaneous : nip19.OfferPriceType.Fixed - const noffer = nip19.nofferEncode({ pubkey, offer: offerStr, priceType, relay }) + const noffer = nip19.nofferEncode({ pubkey, offer: offerStr, priceType, relay, price: offer.price_sats || undefined }) return { label: offer.label, price_sats: offer.price_sats, From babce0d4da58abac6fc9a813eae39005de18fdc3 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 19:08:31 +0000 Subject: [PATCH 10/35] fix zap --- src/services/main/paymentManager.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 09ce4240..81acf2bc 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -575,8 +575,7 @@ export default class { const e = this.parseTags("e", nostrEvent.tags) const relays = this.parseTags("relays", nostrEvent.tags, { required: true, multiples: true }) const amount = this.parseTags("amount", nostrEvent.tags) - if (+amount !== amt) { - console.log({ amount, amt }) + if (amount.length > 0 && +amount[0] !== amt) { throw new Error("amount mismatch") } return { pub: p[0], eventId: e.length > 0 ? e[0] : "", relays, description: event } From 299f5d86b68114d9772fcecc153342fadf22249c Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 19:24:02 +0000 Subject: [PATCH 11/35] list offer ops --- proto/autogenerated/client.md | 26 +++++++ proto/autogenerated/go/http_client.go | 30 ++++++++ proto/autogenerated/go/types.go | 14 ++++ proto/autogenerated/ts/express_server.ts | 34 ++++++++ proto/autogenerated/ts/http_client.ts | 14 ++++ proto/autogenerated/ts/nostr_client.ts | 15 ++++ proto/autogenerated/ts/nostr_transport.ts | 28 +++++++ proto/autogenerated/ts/types.ts | 94 ++++++++++++++++++++++- proto/service/methods.proto | 6 ++ proto/service/structs.proto | 17 ++++ src/services/main/offerManager.ts | 21 +++-- src/services/serverMethods/index.ts | 7 ++ src/services/storage/paymentStorage.ts | 8 ++ 13 files changed, 307 insertions(+), 7 deletions(-) diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 12ec7d3c..988fe113 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -168,6 +168,11 @@ The nostr server will send back a message response, and inside the body there wi - input: [OfferId](#OfferId) - output: [OfferConfig](#OfferConfig) +- GetUserOfferInvoices + - auth type: __User__ + - input: [GetUserOfferInvoicesReq](#GetUserOfferInvoicesReq) + - output: [OfferInvoices](#OfferInvoices) + - GetUserOffers - auth type: __User__ - This methods has an __empty__ __request__ body @@ -585,6 +590,13 @@ The nostr server will send back a message response, and inside the body there wi - input: [OfferId](#OfferId) - output: [OfferConfig](#OfferConfig) +- GetUserOfferInvoices + - auth type: __User__ + - http method: __post__ + - http route: __/api/user/offer/get/invoices__ + - input: [GetUserOfferInvoicesReq](#GetUserOfferInvoicesReq) + - output: [OfferInvoices](#OfferInvoices) + - GetUserOffers - auth type: __User__ - http method: __get__ @@ -998,6 +1010,10 @@ The nostr server will send back a message response, and inside the body there wi ### GetProductBuyLinkResponse - __link__: _string_ +### GetUserOfferInvoicesReq + - __include_unpaid__: _boolean_ + - __offer_id__: _string_ + ### GetUserOperationsRequest - __latestIncomingInvoice__: _number_ - __latestIncomingTx__: _number_ @@ -1130,6 +1146,16 @@ The nostr server will send back a message response, and inside the body there wi ### OfferId - __offer_id__: _string_ +### OfferInvoice + - __amount__: _number_ + - __data__: MAP with key: _string_ and value: _string_ + - __invoice__: _string_ + - __offer_id__: _string_ + - __paid_at_unix__: _number_ + +### OfferInvoices + - __invoices__: ARRAY of: _[OfferInvoice](#OfferInvoice)_ + ### OpenChannel - __active__: _boolean_ - __capacity__: _number_ diff --git a/proto/autogenerated/go/http_client.go b/proto/autogenerated/go/http_client.go index a5a0806f..8319449a 100644 --- a/proto/autogenerated/go/http_client.go +++ b/proto/autogenerated/go/http_client.go @@ -95,6 +95,7 @@ type Client struct { GetUsageMetrics func() (*UsageMetrics, error) GetUserInfo func() (*UserInfo, error) GetUserOffer func(req OfferId) (*OfferConfig, error) + GetUserOfferInvoices func(req GetUserOfferInvoicesReq) (*OfferInvoices, error) GetUserOffers func() (*UserOffers, error) GetUserOperations func(req GetUserOperationsRequest) (*GetUserOperationsResponse, error) HandleLnurlAddress func(routeParams HandleLnurlAddress_RouteParams) (*LnurlPayInfoResponse, error) @@ -1110,6 +1111,35 @@ func NewClient(params ClientParams) *Client { } return &res, nil }, + GetUserOfferInvoices: func(req GetUserOfferInvoicesReq) (*OfferInvoices, error) { + auth, err := params.RetrieveUserAuth() + if err != nil { + return nil, err + } + finalRoute := "/api/user/offer/get/invoices" + body, err := json.Marshal(req) + if err != nil { + return nil, err + } + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return nil, err + } + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := OfferInvoices{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, GetUserOffers: func() (*UserOffers, error) { auth, err := params.RetrieveUserAuth() if err != nil { diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 27e77fd5..28113249 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -274,6 +274,10 @@ type GetPaymentStateRequest struct { type GetProductBuyLinkResponse struct { Link string `json:"link"` } +type GetUserOfferInvoicesReq struct { + Include_unpaid bool `json:"include_unpaid"` + Offer_id string `json:"offer_id"` +} type GetUserOperationsRequest struct { Latestincominginvoice int64 `json:"latestIncomingInvoice"` Latestincomingtx int64 `json:"latestIncomingTx"` @@ -406,6 +410,16 @@ type OfferConfig struct { type OfferId struct { Offer_id string `json:"offer_id"` } +type OfferInvoice struct { + Amount int64 `json:"amount"` + Data map[string]string `json:"data"` + Invoice string `json:"invoice"` + Offer_id string `json:"offer_id"` + Paid_at_unix int64 `json:"paid_at_unix"` +} +type OfferInvoices struct { + Invoices []OfferInvoice `json:"invoices"` +} type OpenChannel struct { Active bool `json:"active"` Capacity int64 `json:"capacity"` diff --git a/proto/autogenerated/ts/express_server.ts b/proto/autogenerated/ts/express_server.ts index 3c418d2d..d84f9c48 100644 --- a/proto/autogenerated/ts/express_server.ts +++ b/proto/autogenerated/ts/express_server.ts @@ -467,6 +467,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'GetUserOfferInvoices': + if (!methods.GetUserOfferInvoices) { + throw new Error('method GetUserOfferInvoices not found' ) + } else { + const error = Types.GetUserOfferInvoicesReqValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + const res = await methods.GetUserOfferInvoices({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'GetUserOffers': if (!methods.GetUserOffers) { throw new Error('method GetUserOffers not found' ) @@ -1135,6 +1147,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.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices is not implemented') + app.post('/api/user/offer/get/invoices', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'GetUserOfferInvoices', 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.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices 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.GetUserOfferInvoicesReqValidate(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 + const response = await methods.GetUserOfferInvoices({rpcName:'GetUserOfferInvoices', ctx:authContext , req: request}) + 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.GetUserOffers) throw new Error('method: GetUserOffers is not implemented') app.get('/api/user/offers/get', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'GetUserOffers', batch: false, nostr: false, batchSize: 0} diff --git a/proto/autogenerated/ts/http_client.ts b/proto/autogenerated/ts/http_client.ts index 5ca384df..01e4266b 100644 --- a/proto/autogenerated/ts/http_client.ts +++ b/proto/autogenerated/ts/http_client.ts @@ -522,6 +522,20 @@ export default (params: ClientParams) => ({ } return { status: 'ERROR', reason: 'invalid response' } }, + GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise => { + const auth = await params.retrieveUserAuth() + if (auth === null) throw new Error('retrieveUserAuth() returned null') + let finalRoute = '/api/user/offer/get/invoices' + 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') { + const result = data + if(!params.checkResult) return { status: 'OK', ...result } + const error = Types.OfferInvoicesValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetUserOffers: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') diff --git a/proto/autogenerated/ts/nostr_client.ts b/proto/autogenerated/ts/nostr_client.ts index 5cf127d2..ff4eda05 100644 --- a/proto/autogenerated/ts/nostr_client.ts +++ b/proto/autogenerated/ts/nostr_client.ts @@ -451,6 +451,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ } return { status: 'ERROR', reason: 'invalid response' } }, + GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise => { + 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:'GetUserOfferInvoices',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.OfferInvoicesValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetUserOffers: async (): Promise => { const auth = await params.retrieveNostrUserAuth() if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') diff --git a/proto/autogenerated/ts/nostr_transport.ts b/proto/autogenerated/ts/nostr_transport.ts index e651e11f..99cceb6d 100644 --- a/proto/autogenerated/ts/nostr_transport.ts +++ b/proto/autogenerated/ts/nostr_transport.ts @@ -349,6 +349,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'GetUserOfferInvoices': + if (!methods.GetUserOfferInvoices) { + throw new Error('method not defined: GetUserOfferInvoices') + } else { + const error = Types.GetUserOfferInvoicesReqValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + const res = await methods.GetUserOfferInvoices({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'GetUserOffers': if (!methods.GetUserOffers) { throw new Error('method not defined: GetUserOffers') @@ -816,6 +828,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 'GetUserOfferInvoices': + try { + if (!methods.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices 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.GetUserOfferInvoicesReqValidate(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.GetUserOfferInvoices({rpcName:'GetUserOfferInvoices', 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 'GetUserOffers': try { if (!methods.GetUserOffers) throw new Error('method: GetUserOffers is not implemented') diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index ef7d35cf..f7a9b021 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -34,8 +34,8 @@ export type UserContext = { app_user_id: string user_id: string } -export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeDebit_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input -export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeDebit_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output +export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeDebit_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input +export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeDebit_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output export type AuthContext = AdminContext | AppContext | GuestContext | GuestWithPubContext | MetricsContext | UserContext export type AddApp_Input = {rpcName:'AddApp', req: AddAppRequest} @@ -167,6 +167,9 @@ export type GetUserInfo_Output = ResultError | ({ status: 'OK' } & UserInfo) export type GetUserOffer_Input = {rpcName:'GetUserOffer', req: OfferId} export type GetUserOffer_Output = ResultError | ({ status: 'OK' } & OfferConfig) +export type GetUserOfferInvoices_Input = {rpcName:'GetUserOfferInvoices', req: GetUserOfferInvoicesReq} +export type GetUserOfferInvoices_Output = ResultError | ({ status: 'OK' } & OfferInvoices) + export type GetUserOffers_Input = {rpcName:'GetUserOffers'} export type GetUserOffers_Output = ResultError | ({ status: 'OK' } & UserOffers) @@ -314,6 +317,7 @@ export type ServerMethods = { GetUsageMetrics?: (req: GetUsageMetrics_Input & {ctx: MetricsContext }) => Promise GetUserInfo?: (req: GetUserInfo_Input & {ctx: UserContext }) => Promise GetUserOffer?: (req: GetUserOffer_Input & {ctx: UserContext }) => Promise + GetUserOfferInvoices?: (req: GetUserOfferInvoices_Input & {ctx: UserContext }) => Promise GetUserOffers?: (req: GetUserOffers_Input & {ctx: UserContext }) => Promise GetUserOperations?: (req: GetUserOperations_Input & {ctx: UserContext }) => Promise HandleLnurlAddress?: (req: HandleLnurlAddress_Input & {ctx: GuestContext }) => Promise @@ -1504,6 +1508,29 @@ export const GetProductBuyLinkResponseValidate = (o?: GetProductBuyLinkResponse, return null } +export type GetUserOfferInvoicesReq = { + include_unpaid: boolean + offer_id: string +} +export const GetUserOfferInvoicesReqOptionalFields: [] = [] +export type GetUserOfferInvoicesReqOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + include_unpaid_CustomCheck?: (v: boolean) => boolean + offer_id_CustomCheck?: (v: string) => boolean +} +export const GetUserOfferInvoicesReqValidate = (o?: GetUserOfferInvoicesReq, opts: GetUserOfferInvoicesReqOptions = {}, path: string = 'GetUserOfferInvoicesReq::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.include_unpaid !== 'boolean') return new Error(`${path}.include_unpaid: is not a boolean`) + if (opts.include_unpaid_CustomCheck && !opts.include_unpaid_CustomCheck(o.include_unpaid)) return new Error(`${path}.include_unpaid: custom check failed`) + + if (typeof o.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`) + if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`) + + return null +} + export type GetUserOperationsRequest = { latestIncomingInvoice: number latestIncomingTx: number @@ -2310,6 +2337,69 @@ export const OfferIdValidate = (o?: OfferId, opts: OfferIdOptions = {}, path: st return null } +export type OfferInvoice = { + amount: number + data: Record + invoice: string + offer_id: string + paid_at_unix: number +} +export const OfferInvoiceOptionalFields: [] = [] +export type OfferInvoiceOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + amount_CustomCheck?: (v: number) => boolean + data_CustomCheck?: (v: Record) => boolean + invoice_CustomCheck?: (v: string) => boolean + offer_id_CustomCheck?: (v: string) => boolean + paid_at_unix_CustomCheck?: (v: number) => boolean +} +export const OfferInvoiceValidate = (o?: OfferInvoice, opts: OfferInvoiceOptions = {}, path: string = 'OfferInvoice::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.amount !== 'number') return new Error(`${path}.amount: is not a number`) + if (opts.amount_CustomCheck && !opts.amount_CustomCheck(o.amount)) return new Error(`${path}.amount: custom check failed`) + + if (typeof o.data !== 'object' || o.data === null) return new Error(`${path}.data: is not an object or is null`) + for (const key in o.data) { + if (typeof o.data[key] !== 'string') return new Error(`${path}.data['${key}']: is not a string`) + } + + if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`) + if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`) + + if (typeof o.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`) + if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`) + + if (typeof o.paid_at_unix !== 'number') return new Error(`${path}.paid_at_unix: is not a number`) + if (opts.paid_at_unix_CustomCheck && !opts.paid_at_unix_CustomCheck(o.paid_at_unix)) return new Error(`${path}.paid_at_unix: custom check failed`) + + return null +} + +export type OfferInvoices = { + invoices: OfferInvoice[] +} +export const OfferInvoicesOptionalFields: [] = [] +export type OfferInvoicesOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + invoices_ItemOptions?: OfferInvoiceOptions + invoices_CustomCheck?: (v: OfferInvoice[]) => boolean +} +export const OfferInvoicesValidate = (o?: OfferInvoices, opts: OfferInvoicesOptions = {}, path: string = 'OfferInvoices::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.invoices)) return new Error(`${path}.invoices: is not an array`) + for (let index = 0; index < o.invoices.length; index++) { + const invoicesErr = OfferInvoiceValidate(o.invoices[index], opts.invoices_ItemOptions, `${path}.invoices[${index}]`) + if (invoicesErr !== null) return invoicesErr + } + if (opts.invoices_CustomCheck && !opts.invoices_CustomCheck(o.invoices)) return new Error(`${path}.invoices: custom check failed`) + + return null +} + export type OpenChannel = { active: boolean capacity: number diff --git a/proto/service/methods.proto b/proto/service/methods.proto index b5a91007..4ac703ad 100644 --- a/proto/service/methods.proto +++ b/proto/service/methods.proto @@ -481,6 +481,12 @@ service LightningPub { option (http_route) = "/api/user/offer/get"; option (nostr) = true; } + rpc GetUserOfferInvoices(structs.GetUserOfferInvoicesReq) returns (structs.OfferInvoices){ + option (auth_type) = "User"; + option (http_method) = "post"; + option (http_route) = "/api/user/offer/get/invoices"; + option (nostr) = true; + } rpc UpdateUserOffer(structs.OfferConfig) returns (structs.Empty){ option (auth_type) = "User"; diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 955b113a..409c1b16 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -641,4 +641,21 @@ message OfferConfig { message UserOffers { repeated OfferConfig offers = 1; +} + +message GetUserOfferInvoicesReq { + string offer_id = 1; + bool include_unpaid = 2; +} + +message OfferInvoices { + repeated OfferInvoice invoices = 1; +} + +message OfferInvoice { + string invoice = 1; + string offer_id = 2; + int64 paid_at_unix = 3; + int64 amount = 4; + map data = 5; } \ No newline at end of file diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts index fc31d441..e1a4a3e1 100644 --- a/src/services/main/offerManager.ts +++ b/src/services/main/offerManager.ts @@ -44,11 +44,6 @@ const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay } export class OfferManager { - - - - - _nostrSend: NostrSend | null = null applicationManager: ApplicationManager @@ -97,6 +92,22 @@ export class OfferManager { callback_url: req.callback_url, }) } + async GetUserOfferInvoices(ctx: Types.UserContext, req: Types.GetUserOfferInvoicesReq): Promise { + const userOffer = await this.storage.offerStorage.GetUserOffer(ctx.app_user_id, req.offer_id) + if (!userOffer) { + throw new Error("Offer not found") + } + const i = await this.storage.paymentStorage.GetOfferInvoices(req.offer_id, req.include_unpaid) + return { + invoices: i.map(i => ({ + invoice: i.invoice, + offer_id: i.offer_id || "", + paid_at_unix: i.paid_at_unix, + amount: i.paid_amount, + data: i.payer_data || {} + })) + } + } async GetUserOffer(ctx: Types.UserContext, req: Types.OfferId): Promise { const app = await this.applicationManager.GetApp(ctx.app_id) diff --git a/src/services/serverMethods/index.ts b/src/services/serverMethods/index.ts index 7a9b0e0c..4fdb6c76 100644 --- a/src/services/serverMethods/index.ts +++ b/src/services/serverMethods/index.ts @@ -350,6 +350,13 @@ export default (mainHandler: Main): Types.ServerMethods => { }) if (err != null) throw new Error(err.message) return mainHandler.offerManager.GetUserOffer(ctx, req) + }, + GetUserOfferInvoices: async ({ ctx, req }) => { + const err = Types.GetUserOfferInvoicesReqValidate(req, { + offer_id_CustomCheck: id => id !== '' + }) + if (err != null) throw new Error(err.message) + return mainHandler.offerManager.GetUserOfferInvoices(ctx, req) } } } \ No newline at end of file diff --git a/src/services/storage/paymentStorage.ts b/src/services/storage/paymentStorage.ts index 051b9943..9e7755c3 100644 --- a/src/services/storage/paymentStorage.ts +++ b/src/services/storage/paymentStorage.ts @@ -454,4 +454,12 @@ export default class { async GetPendingPayments(entityManager = this.DB) { return entityManager.getRepository(UserInvoicePayment).find({ where: { paid_at_unix: 0 } }) } + + async GetOfferInvoices(offerId: string, includeUnpaid: boolean, entityManager = this.DB) { + const where: { offer_id: string, paid_at_unix?: FindOperator } = { offer_id: offerId } + if (!includeUnpaid) { + where.paid_at_unix = MoreThan(0) + } + return entityManager.getRepository(UserReceivingInvoice).find({ where }) + } } \ No newline at end of file From a13ddda0257dfd93b161fb92d559b482de78b652 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 20:06:31 +0000 Subject: [PATCH 12/35] cb url template --- proto/autogenerated/ts/http_client.ts | 338 +++++++++++++------------- src/services/main/index.ts | 10 +- 2 files changed, 177 insertions(+), 171 deletions(-) diff --git a/proto/autogenerated/ts/http_client.ts b/proto/autogenerated/ts/http_client.ts index 01e4266b..ab40fdae 100644 --- a/proto/autogenerated/ts/http_client.ts +++ b/proto/autogenerated/ts/http_client.ts @@ -17,57 +17,57 @@ export type ClientParams = { checkResult?: true } export default (params: ClientParams) => ({ - AddApp: async (request: Types.AddAppRequest): Promise => { + AddApp: async (request: Types.AddAppRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/app/add' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + 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' } }, - AddAppInvoice: async (request: Types.AddAppInvoiceRequest): Promise => { + AddAppInvoice: async (request: Types.AddAppInvoiceRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/add/invoice' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.NewInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - AddAppUser: async (request: Types.AddAppUserRequest): Promise => { + AddAppUser: async (request: Types.AddAppUserRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/add' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.AppUserValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - AddAppUserInvoice: async (request: Types.AddAppUserInvoiceRequest): Promise => { + AddAppUserInvoice: async (request: Types.AddAppUserInvoiceRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/add/invoice' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.NewInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -79,62 +79,62 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/admin/peer' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - AddProduct: async (request: Types.AddProductRequest): Promise => { + AddProduct: async (request: Types.AddProductRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/product/add' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.ProductValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - AddUserOffer: async (request: Types.OfferConfig): Promise => { + AddUserOffer: async (request: Types.OfferConfig): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offer/add' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.OfferIdValidate(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 => { + AuthApp: async (request: Types.AuthAppRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/app/auth' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + 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' } }, - AuthorizeDebit: async (request: Types.DebitAuthorizationRequest): Promise => { + AuthorizeDebit: async (request: Types.DebitAuthorizationRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/debit/authorize' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.DebitAuthorizationValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -146,73 +146,73 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/debit/ban' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - BanUser: async (request: Types.BanUserRequest): Promise => { + BanUser: async (request: Types.BanUserRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/user/ban' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + 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' } }, - BatchUser: async (requests:Types.UserMethodInputs[]): Promise => { + BatchUser: async (requests: Types.UserMethodInputs[]): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/batch' - const { data } = await axios.post(params.baseUrl + finalRoute, {requests}, { headers: { 'authorization': auth } }) + const { data } = await axios.post(params.baseUrl + finalRoute, { requests }, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { return { status: 'OK', ...data } - } + } return { status: 'ERROR', reason: 'invalid response' } }, - CloseChannel: async (request: Types.CloseChannelRequest): Promise => { + CloseChannel: async (request: Types.CloseChannelRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/channel/close' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.CloseChannelResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - CreateOneTimeInviteLink: async (request: Types.CreateOneTimeInviteLinkRequest): Promise => { + CreateOneTimeInviteLink: async (request: Types.CreateOneTimeInviteLinkRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/app/invite/create' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.CreateOneTimeInviteLinkResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - DecodeInvoice: async (request: Types.DecodeInvoiceRequest): Promise => { + DecodeInvoice: async (request: Types.DecodeInvoiceRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/invoice/decode' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.DecodeInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -224,7 +224,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/offer/delete' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -235,7 +235,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/debit/edit' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -246,7 +246,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/encryption/exchange' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -257,127 +257,127 @@ export default (params: ClientParams) => ({ 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - GetApp: async (): Promise => { + GetApp: async (): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/get' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.ApplicationValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetAppUser: async (request: Types.GetAppUserRequest): Promise => { + GetAppUser: async (request: Types.GetAppUserRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/get' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.AppUserValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetAppUserLNURLInfo: async (request: Types.GetAppUserLNURLInfoRequest): Promise => { + GetAppUserLNURLInfo: async (request: Types.GetAppUserLNURLInfoRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/lnurl/pay/info' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlPayInfoResponseValidate(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 => { + GetAppsMetrics: async (request: Types.AppsMetricsRequest): Promise => { const auth = await params.retrieveMetricsAuth() if (auth === null) throw new Error('retrieveMetricsAuth() returned null') let finalRoute = '/api/reports/apps' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + 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' } }, - GetDebitAuthorizations: async (): Promise => { + GetDebitAuthorizations: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/debit/get' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.DebitAuthorizationsValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetHttpCreds: async (cb: (v:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise => { throw new Error('http streams are not supported')}, - GetInviteLinkState: async (request: Types.GetInviteTokenStateRequest): Promise => { + GetHttpCreds: async (cb: (v: ResultError | ({ status: 'OK' } & Types.HttpCreds)) => void): Promise => { throw new Error('http streams are not supported') }, + GetInviteLinkState: async (request: Types.GetInviteTokenStateRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/app/invite/get' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.GetInviteTokenStateResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLNURLChannelLink: async (): Promise => { + GetLNURLChannelLink: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/lnurl_channel/url' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlLinkResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLiveDebitRequests: async (cb: (v:ResultError | ({ status: 'OK' }& Types.LiveDebitRequest)) => void): Promise => { throw new Error('http streams are not supported')}, - GetLiveUserOperations: async (cb: (v:ResultError | ({ status: 'OK' }& Types.LiveUserOperation)) => void): Promise => { throw new Error('http streams are not supported')}, - GetLndMetrics: async (request: Types.LndMetricsRequest): Promise => { + GetLiveDebitRequests: async (cb: (v: ResultError | ({ status: 'OK' } & Types.LiveDebitRequest)) => void): Promise => { throw new Error('http streams are not supported') }, + GetLiveUserOperations: async (cb: (v: ResultError | ({ status: 'OK' } & Types.LiveUserOperation)) => void): Promise => { throw new Error('http streams are not supported') }, + GetLndMetrics: async (request: Types.LndMetricsRequest): Promise => { const auth = await params.retrieveMetricsAuth() if (auth === null) throw new Error('retrieveMetricsAuth() returned null') let finalRoute = '/api/reports/lnd' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + 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' } }, - GetLnurlPayInfo: async (query: Types.GetLnurlPayInfo_Query): Promise => { + GetLnurlPayInfo: async (query: Types.GetLnurlPayInfo_Query): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') let finalRoute = '/api/guest/lnurl_pay/info' @@ -385,29 +385,29 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlPayInfoResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLnurlPayLink: async (): Promise => { + GetLnurlPayLink: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/lnurl_pay/link' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlLinkResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLnurlWithdrawInfo: async (query: Types.GetLnurlWithdrawInfo_Query): Promise => { + GetLnurlWithdrawInfo: async (query: Types.GetLnurlWithdrawInfo_Query): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') let finalRoute = '/api/guest/lnurl_withdraw/info' @@ -415,171 +415,171 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlWithdrawInfoResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLnurlWithdrawLink: async (): Promise => { + GetLnurlWithdrawLink: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/lnurl_withdraw/link' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlLinkResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetMigrationUpdate: async (cb: (v:ResultError | ({ status: 'OK' }& Types.MigrationUpdate)) => void): Promise => { throw new Error('http streams are not supported')}, - GetNPubLinkingState: async (request: Types.GetNPubLinking): Promise => { + GetMigrationUpdate: async (cb: (v: ResultError | ({ status: 'OK' } & Types.MigrationUpdate)) => void): Promise => { throw new Error('http streams are not supported') }, + GetNPubLinkingState: async (request: Types.GetNPubLinking): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/npub/state' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.NPubLinkingValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetPaymentState: async (request: Types.GetPaymentStateRequest): Promise => { + GetPaymentState: async (request: Types.GetPaymentStateRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/payment/state' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.PaymentStateValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetSeed: async (): Promise => { + GetSeed: async (): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/seed' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LndSeedValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUsageMetrics: async (): Promise => { + GetUsageMetrics: async (): Promise => { const auth = await params.retrieveMetricsAuth() if (auth === null) throw new Error('retrieveMetricsAuth() returned null') let finalRoute = '/api/reports/usage' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + 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' } }, - GetUserInfo: async (): Promise => { + GetUserInfo: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/info' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.UserInfoValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUserOffer: async (request: Types.OfferId): Promise => { + GetUserOffer: async (request: Types.OfferId): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offer/get' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.OfferConfigValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise => { + GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offer/get/invoices' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.OfferInvoicesValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUserOffers: async (): Promise => { + GetUserOffers: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offers/get' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.UserOffersValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise => { + GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/operations' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.GetUserOperationsResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - HandleLnurlAddress: async (routeParams: Types.HandleLnurlAddress_RouteParams): Promise => { + HandleLnurlAddress: async (routeParams: Types.HandleLnurlAddress_RouteParams): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') let finalRoute = '/.well-known/lnurlp/:address_name' finalRoute = finalRoute.replace(':address_name', routeParams['address_name']) 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlPayInfoResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - HandleLnurlPay: async (query: Types.HandleLnurlPay_Query): Promise => { + HandleLnurlPay: async (query: Types.HandleLnurlPay_Query): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') let finalRoute = '/api/guest/lnurl_pay/handle' @@ -587,9 +587,9 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.HandleLnurlPayResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -603,7 +603,7 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -614,7 +614,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/health' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -625,68 +625,68 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/guest/npub/link' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - ListChannels: async (): Promise => { + ListChannels: async (): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/channels' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.LndChannelsValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - LndGetInfo: async (request: Types.LndGetInfoRequest): Promise => { + LndGetInfo: async (request: Types.LndGetInfoRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/lnd/getinfo' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + 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' } }, - NewAddress: async (request: Types.NewAddressRequest): Promise => { + NewAddress: async (request: Types.NewAddressRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/chain/new' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.NewAddressResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - NewInvoice: async (request: Types.NewInvoiceRequest): Promise => { + NewInvoice: async (request: Types.NewInvoiceRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/invoice/new' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.NewInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise => { + NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/product/get/invoice' @@ -694,79 +694,79 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.NewInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - OpenChannel: async (request: Types.OpenChannelRequest): Promise => { + OpenChannel: async (request: Types.OpenChannelRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/channel/open' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.OpenChannelResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - PayAddress: async (request: Types.PayAddressRequest): Promise => { + PayAddress: async (request: Types.PayAddressRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/chain/pay' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.PayAddressResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - PayAppUserInvoice: async (request: Types.PayAppUserInvoiceRequest): Promise => { + PayAppUserInvoice: async (request: Types.PayAppUserInvoiceRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/invoice/pay' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.PayInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - PayInvoice: async (request: Types.PayInvoiceRequest): Promise => { + PayInvoice: async (request: Types.PayInvoiceRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/invoice/pay' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.PayInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - RequestNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise => { + RequestNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/npub/token' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.RequestNPubLinkingTokenResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -778,20 +778,20 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/debit/reset' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - ResetNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise => { + ResetNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/npub/token/reset' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.RequestNPubLinkingTokenResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -803,7 +803,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/debit/finish' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -814,7 +814,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/app/internal/pay' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -825,7 +825,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/app/user/internal/pay' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -836,7 +836,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/app/mock/blance/set' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -847,7 +847,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/app/mock/user/blance/set' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -858,20 +858,20 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/lnd/mock/invoice/paid' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise => { + UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/cb/update' 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') { + if (data.status === 'OK') { const result = data - if(!params.checkResult) return { status: 'OK', ...result } + if (!params.checkResult) return { status: 'OK', ...result } const error = Types.CallbackUrlValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -883,7 +883,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/admin/channel/policy/update' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -894,7 +894,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/offer/update' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -905,7 +905,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/guest/invite' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -916,7 +916,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/health' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } diff --git a/src/services/main/index.ts b/src/services/main/index.ts index 45ef3d8f..1b6782b1 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -220,7 +220,7 @@ export default class { if (fee > 0) { await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, 'fees', tx) } - await this.triggerPaidCallback(log, userInvoice.callbackUrl) + await this.triggerPaidCallback(log, userInvoice.callbackUrl, { invoice: paymentRequest, amount, other: userInvoice.payer_data }) const operationId = `${Types.UserOperationType.INCOMING_INVOICE}-${userInvoice.serial_id}` const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee, confirmed: true, tx_hash: "", internal } this.sendOperationToNostr(userInvoice.linkedApplication, userInvoice.user.user_id, op) @@ -234,10 +234,16 @@ export default class { }) } - async triggerPaidCallback(log: PubLogger, url: string) { + async triggerPaidCallback(log: PubLogger, url: string, { invoice, amount, other }: { invoice: string, amount: number, other?: Record }) { if (!url) { return } + url.replace(`%[invoice]`, invoice).replace(`%[amount]`, amount.toString()) + if (other) { + for (const [key, value] of Object.entries(other)) { + url.replace(`%[${key}]`, value) + } + } try { const symbol = url.includes('?') ? "&" : "?" await fetch(url + symbol + "ok=true") From 2328dfe366ac321011783a00719a638056c8ac92 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 20:11:17 +0000 Subject: [PATCH 13/35] up --- src/services/main/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/main/index.ts b/src/services/main/index.ts index 1b6782b1..17dc0da0 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -246,7 +246,9 @@ export default class { } try { const symbol = url.includes('?') ? "&" : "?" - await fetch(url + symbol + "ok=true") + const finalUrl = url + symbol + "ok=true" + log("sending paid callback to", finalUrl) + await fetch(finalUrl) } catch (err: any) { log(ERROR, "error sending paid callback for invoice", err.message || "") } From d625c4512fe10b7990652824043851de62dad969 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 20:11:56 +0000 Subject: [PATCH 14/35] up --- src/services/main/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/services/main/index.ts b/src/services/main/index.ts index 17dc0da0..c7a87d4c 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -224,7 +224,11 @@ export default class { const operationId = `${Types.UserOperationType.INCOMING_INVOICE}-${userInvoice.serial_id}` const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee, confirmed: true, tx_hash: "", internal } this.sendOperationToNostr(userInvoice.linkedApplication, userInvoice.user.user_id, op) - this.createZapReceipt(log, userInvoice) + try { + this.createZapReceipt(log, userInvoice) + } catch (err: any) { + log(ERROR, "cannot create zap receipt", err.message || "") + } this.liquidityManager.afterInInvoicePaid() this.utils.stateBundler.AddTxPoint('invoiceWasPaid', amount, { used, from: 'system', timeDiscount: true }) } catch (err: any) { From 73db820217dbc515f65895c27e76081ddc96aaca Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 20:20:57 +0000 Subject: [PATCH 15/35] fix --- src/services/main/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/main/index.ts b/src/services/main/index.ts index c7a87d4c..dd2bd6ba 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -242,15 +242,15 @@ export default class { if (!url) { return } - url.replace(`%[invoice]`, invoice).replace(`%[amount]`, amount.toString()) + let finalUrl = url.replace(`%[invoice]`, invoice).replace(`%[amount]`, amount.toString()) if (other) { for (const [key, value] of Object.entries(other)) { - url.replace(`%[${key}]`, value) + finalUrl = url.replace(`%[${key}]`, value) } } try { const symbol = url.includes('?') ? "&" : "?" - const finalUrl = url + symbol + "ok=true" + finalUrl = finalUrl + symbol + "ok=true" log("sending paid callback to", finalUrl) await fetch(finalUrl) } catch (err: any) { From fa8a8272d73adaf81c82cab29f62a481c0aa36c8 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 20:23:24 +0000 Subject: [PATCH 16/35] up --- src/services/main/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/main/index.ts b/src/services/main/index.ts index dd2bd6ba..bae93526 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -242,7 +242,8 @@ export default class { if (!url) { return } - let finalUrl = url.replace(`%[invoice]`, invoice).replace(`%[amount]`, amount.toString()) + let finalUrl = url.replace(`%[invoice]`, invoice) + finalUrl = finalUrl.replace(`%[amount]`, amount.toString()) if (other) { for (const [key, value] of Object.entries(other)) { finalUrl = url.replace(`%[${key}]`, value) From 272cb38f1a2eb37f6df661a677d56ca9ad6316a8 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 20:24:06 +0000 Subject: [PATCH 17/35] fix --- src/services/main/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/services/main/index.ts b/src/services/main/index.ts index bae93526..e2f4eb92 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -242,15 +242,14 @@ export default class { if (!url) { return } - let finalUrl = url.replace(`%[invoice]`, invoice) - finalUrl = finalUrl.replace(`%[amount]`, amount.toString()) + let finalUrl = url.replace(`%[invoice]`, invoice).replace(`%[amount]`, amount.toString()) if (other) { for (const [key, value] of Object.entries(other)) { - finalUrl = url.replace(`%[${key}]`, value) + finalUrl = finalUrl.replace(`%[${key}]`, value) } } try { - const symbol = url.includes('?') ? "&" : "?" + const symbol = finalUrl.includes('?') ? "&" : "?" finalUrl = finalUrl + symbol + "ok=true" log("sending paid callback to", finalUrl) await fetch(finalUrl) From 84b3ef4b9d9b1ff15aeeee702cd7b45668c39f83 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 12 Dec 2024 18:56:21 +0000 Subject: [PATCH 18/35] lnd health --- src/services/serverMethods/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/serverMethods/index.ts b/src/services/serverMethods/index.ts index 4fdb6c76..1b4a2832 100644 --- a/src/services/serverMethods/index.ts +++ b/src/services/serverMethods/index.ts @@ -73,7 +73,7 @@ export default (mainHandler: Main): Types.ServerMethods => { if (err != null) throw new Error(err.message) await mainHandler.paymentManager.SetMockInvoiceAsPaid(req) }, - UserHealth: async () => { }, + UserHealth: async () => { await mainHandler.lnd.Health() }, GetUserInfo: ({ ctx }) => mainHandler.appUserManager.GetUserInfo(ctx), UpdateCallbackUrl: async ({ ctx, req }) => { return mainHandler.appUserManager.UpdateCallbackUrl(ctx, req) From 93af9969ed4df54d9d7c20496501643904162bc2 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 12 Dec 2024 18:59:25 +0000 Subject: [PATCH 19/35] user health no err --- proto/autogenerated/client.md | 7 +- proto/autogenerated/go/http_client.go | 19 +- proto/autogenerated/go/types.go | 3 + proto/autogenerated/ts/express_server.ts | 6 +- proto/autogenerated/ts/http_client.ts | 345 +++++++++++----------- proto/autogenerated/ts/nostr_client.ts | 7 +- proto/autogenerated/ts/nostr_transport.ts | 6 +- proto/autogenerated/ts/types.ts | 22 +- proto/service/methods.proto | 2 +- proto/service/structs.proto | 4 + src/services/serverMethods/index.ts | 5 +- 11 files changed, 234 insertions(+), 192 deletions(-) diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 988fe113..65c78245 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -263,7 +263,7 @@ The nostr server will send back a message response, and inside the body there wi - UserHealth - auth type: __User__ - This methods has an __empty__ __request__ body - - This methods has an __empty__ __response__ body + - output: [UserHealthState](#UserHealthState) # HTTP API DEFINITION @@ -817,7 +817,7 @@ The nostr server will send back a message response, and inside the body there wi - http method: __post__ - http route: __/api/user/health__ - This methods has an __empty__ __request__ body - - This methods has an __empty__ __response__ body + - output: [UserHealthState](#UserHealthState) # INPUTS AND OUTPUTS @@ -1291,6 +1291,9 @@ The nostr server will send back a message response, and inside the body there wi ### UseInviteLinkRequest - __invite_token__: _string_ +### UserHealthState + - __downtime_reason__: _string_ + ### UserInfo - __balance__: _number_ - __bridge_url__: _string_ diff --git a/proto/autogenerated/go/http_client.go b/proto/autogenerated/go/http_client.go index 8319449a..522df237 100644 --- a/proto/autogenerated/go/http_client.go +++ b/proto/autogenerated/go/http_client.go @@ -125,7 +125,7 @@ type Client struct { UpdateChannelPolicy func(req UpdateChannelPolicyRequest) error UpdateUserOffer func(req OfferConfig) error UseInviteLink func(req UseInviteLinkRequest) error - UserHealth func() error + UserHealth func() (*UserHealthState, error) } func NewClient(params ClientParams) *Client { @@ -1904,26 +1904,31 @@ func NewClient(params ClientParams) *Client { } return nil }, - UserHealth: func() error { + UserHealth: func() (*UserHealthState, error) { auth, err := params.RetrieveUserAuth() if err != nil { - return err + return nil, err } finalRoute := "/api/user/health" body := []byte{} resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) if err != nil { - return err + return nil, err } result := ResultError{} err = json.Unmarshal(resBody, &result) if err != nil { - return err + return nil, err } if result.Status == "ERROR" { - return fmt.Errorf(result.Reason) + return nil, fmt.Errorf(result.Reason) } - return nil + res := UserHealthState{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil }, } } diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 28113249..c82ae57f 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -555,6 +555,9 @@ type UsageMetrics struct { type UseInviteLinkRequest struct { Invite_token string `json:"invite_token"` } +type UserHealthState struct { + Downtime_reason string `json:"downtime_reason"` +} type UserInfo struct { Balance int64 `json:"balance"` Bridge_url string `json:"bridge_url"` diff --git a/proto/autogenerated/ts/express_server.ts b/proto/autogenerated/ts/express_server.ts index d84f9c48..f76044ea 100644 --- a/proto/autogenerated/ts/express_server.ts +++ b/proto/autogenerated/ts/express_server.ts @@ -612,7 +612,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { throw new Error('method UserHealth not found' ) } else { opStats.validate = opStats.guard - await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK' }) + const res = await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK', ...res }) opStats.handle = process.hrtime.bigint() callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } @@ -1799,9 +1799,9 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { stats.validate = stats.guard const query = req.query const params = req.params - await methods.UserHealth({rpcName:'UserHealth', ctx:authContext }) + const response = await methods.UserHealth({rpcName:'UserHealth', ctx:authContext }) stats.handle = process.hrtime.bigint() - res.json({status: 'OK'}) + 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 } }) diff --git a/proto/autogenerated/ts/http_client.ts b/proto/autogenerated/ts/http_client.ts index ab40fdae..0097d29e 100644 --- a/proto/autogenerated/ts/http_client.ts +++ b/proto/autogenerated/ts/http_client.ts @@ -17,57 +17,57 @@ export type ClientParams = { checkResult?: true } export default (params: ClientParams) => ({ - AddApp: async (request: Types.AddAppRequest): Promise => { + AddApp: async (request: Types.AddAppRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/app/add' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + 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' } }, - AddAppInvoice: async (request: Types.AddAppInvoiceRequest): Promise => { + AddAppInvoice: async (request: Types.AddAppInvoiceRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/add/invoice' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.NewInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - AddAppUser: async (request: Types.AddAppUserRequest): Promise => { + AddAppUser: async (request: Types.AddAppUserRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/add' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.AppUserValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - AddAppUserInvoice: async (request: Types.AddAppUserInvoiceRequest): Promise => { + AddAppUserInvoice: async (request: Types.AddAppUserInvoiceRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/add/invoice' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.NewInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -79,62 +79,62 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/admin/peer' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - AddProduct: async (request: Types.AddProductRequest): Promise => { + AddProduct: async (request: Types.AddProductRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/product/add' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.ProductValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - AddUserOffer: async (request: Types.OfferConfig): Promise => { + AddUserOffer: async (request: Types.OfferConfig): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offer/add' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.OfferIdValidate(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 => { + AuthApp: async (request: Types.AuthAppRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/app/auth' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + 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' } }, - AuthorizeDebit: async (request: Types.DebitAuthorizationRequest): Promise => { + AuthorizeDebit: async (request: Types.DebitAuthorizationRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/debit/authorize' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.DebitAuthorizationValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -146,73 +146,73 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/debit/ban' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - BanUser: async (request: Types.BanUserRequest): Promise => { + BanUser: async (request: Types.BanUserRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/user/ban' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + 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' } }, - BatchUser: async (requests: Types.UserMethodInputs[]): Promise => { + BatchUser: async (requests:Types.UserMethodInputs[]): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/batch' - const { data } = await axios.post(params.baseUrl + finalRoute, { requests }, { headers: { 'authorization': auth } }) + const { data } = await axios.post(params.baseUrl + finalRoute, {requests}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { return { status: 'OK', ...data } - } + } return { status: 'ERROR', reason: 'invalid response' } }, - CloseChannel: async (request: Types.CloseChannelRequest): Promise => { + CloseChannel: async (request: Types.CloseChannelRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/channel/close' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.CloseChannelResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - CreateOneTimeInviteLink: async (request: Types.CreateOneTimeInviteLinkRequest): Promise => { + CreateOneTimeInviteLink: async (request: Types.CreateOneTimeInviteLinkRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/app/invite/create' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.CreateOneTimeInviteLinkResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - DecodeInvoice: async (request: Types.DecodeInvoiceRequest): Promise => { + DecodeInvoice: async (request: Types.DecodeInvoiceRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/invoice/decode' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.DecodeInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -224,7 +224,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/offer/delete' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -235,7 +235,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/debit/edit' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -246,7 +246,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/encryption/exchange' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -257,127 +257,127 @@ export default (params: ClientParams) => ({ 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - GetApp: async (): Promise => { + GetApp: async (): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/get' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.ApplicationValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetAppUser: async (request: Types.GetAppUserRequest): Promise => { + GetAppUser: async (request: Types.GetAppUserRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/get' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.AppUserValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetAppUserLNURLInfo: async (request: Types.GetAppUserLNURLInfoRequest): Promise => { + GetAppUserLNURLInfo: async (request: Types.GetAppUserLNURLInfoRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/lnurl/pay/info' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlPayInfoResponseValidate(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 => { + GetAppsMetrics: async (request: Types.AppsMetricsRequest): Promise => { const auth = await params.retrieveMetricsAuth() if (auth === null) throw new Error('retrieveMetricsAuth() returned null') let finalRoute = '/api/reports/apps' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + 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' } }, - GetDebitAuthorizations: async (): Promise => { + GetDebitAuthorizations: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/debit/get' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.DebitAuthorizationsValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetHttpCreds: async (cb: (v: ResultError | ({ status: 'OK' } & Types.HttpCreds)) => void): Promise => { throw new Error('http streams are not supported') }, - GetInviteLinkState: async (request: Types.GetInviteTokenStateRequest): Promise => { + GetHttpCreds: async (cb: (v:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise => { throw new Error('http streams are not supported')}, + GetInviteLinkState: async (request: Types.GetInviteTokenStateRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/app/invite/get' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.GetInviteTokenStateResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLNURLChannelLink: async (): Promise => { + GetLNURLChannelLink: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/lnurl_channel/url' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlLinkResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLiveDebitRequests: async (cb: (v: ResultError | ({ status: 'OK' } & Types.LiveDebitRequest)) => void): Promise => { throw new Error('http streams are not supported') }, - GetLiveUserOperations: async (cb: (v: ResultError | ({ status: 'OK' } & Types.LiveUserOperation)) => void): Promise => { throw new Error('http streams are not supported') }, - GetLndMetrics: async (request: Types.LndMetricsRequest): Promise => { + GetLiveDebitRequests: async (cb: (v:ResultError | ({ status: 'OK' }& Types.LiveDebitRequest)) => void): Promise => { throw new Error('http streams are not supported')}, + GetLiveUserOperations: async (cb: (v:ResultError | ({ status: 'OK' }& Types.LiveUserOperation)) => void): Promise => { throw new Error('http streams are not supported')}, + GetLndMetrics: async (request: Types.LndMetricsRequest): Promise => { const auth = await params.retrieveMetricsAuth() if (auth === null) throw new Error('retrieveMetricsAuth() returned null') let finalRoute = '/api/reports/lnd' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + 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' } }, - GetLnurlPayInfo: async (query: Types.GetLnurlPayInfo_Query): Promise => { + GetLnurlPayInfo: async (query: Types.GetLnurlPayInfo_Query): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') let finalRoute = '/api/guest/lnurl_pay/info' @@ -385,29 +385,29 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlPayInfoResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLnurlPayLink: async (): Promise => { + GetLnurlPayLink: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/lnurl_pay/link' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlLinkResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLnurlWithdrawInfo: async (query: Types.GetLnurlWithdrawInfo_Query): Promise => { + GetLnurlWithdrawInfo: async (query: Types.GetLnurlWithdrawInfo_Query): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') let finalRoute = '/api/guest/lnurl_withdraw/info' @@ -415,171 +415,171 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlWithdrawInfoResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetLnurlWithdrawLink: async (): Promise => { + GetLnurlWithdrawLink: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/lnurl_withdraw/link' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlLinkResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetMigrationUpdate: async (cb: (v: ResultError | ({ status: 'OK' } & Types.MigrationUpdate)) => void): Promise => { throw new Error('http streams are not supported') }, - GetNPubLinkingState: async (request: Types.GetNPubLinking): Promise => { + GetMigrationUpdate: async (cb: (v:ResultError | ({ status: 'OK' }& Types.MigrationUpdate)) => void): Promise => { throw new Error('http streams are not supported')}, + GetNPubLinkingState: async (request: Types.GetNPubLinking): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/npub/state' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.NPubLinkingValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetPaymentState: async (request: Types.GetPaymentStateRequest): Promise => { + GetPaymentState: async (request: Types.GetPaymentStateRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/payment/state' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.PaymentStateValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetSeed: async (): Promise => { + GetSeed: async (): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/seed' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LndSeedValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUsageMetrics: async (): Promise => { + GetUsageMetrics: async (): Promise => { const auth = await params.retrieveMetricsAuth() if (auth === null) throw new Error('retrieveMetricsAuth() returned null') let finalRoute = '/api/reports/usage' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + 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' } }, - GetUserInfo: async (): Promise => { + GetUserInfo: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/info' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.UserInfoValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUserOffer: async (request: Types.OfferId): Promise => { + GetUserOffer: async (request: Types.OfferId): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offer/get' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.OfferConfigValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise => { + GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offer/get/invoices' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.OfferInvoicesValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUserOffers: async (): Promise => { + GetUserOffers: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/offers/get' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.UserOffersValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise => { + GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/operations' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.GetUserOperationsResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - HandleLnurlAddress: async (routeParams: Types.HandleLnurlAddress_RouteParams): Promise => { + HandleLnurlAddress: async (routeParams: Types.HandleLnurlAddress_RouteParams): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') let finalRoute = '/.well-known/lnurlp/:address_name' finalRoute = finalRoute.replace(':address_name', routeParams['address_name']) 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LnurlPayInfoResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - HandleLnurlPay: async (query: Types.HandleLnurlPay_Query): Promise => { + HandleLnurlPay: async (query: Types.HandleLnurlPay_Query): Promise => { const auth = await params.retrieveGuestAuth() if (auth === null) throw new Error('retrieveGuestAuth() returned null') let finalRoute = '/api/guest/lnurl_pay/handle' @@ -587,9 +587,9 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.HandleLnurlPayResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -603,7 +603,7 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -614,7 +614,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/health' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -625,68 +625,68 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/guest/npub/link' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - ListChannels: async (): Promise => { + ListChannels: async (): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/channels' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.LndChannelsValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - LndGetInfo: async (request: Types.LndGetInfoRequest): Promise => { + LndGetInfo: async (request: Types.LndGetInfoRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/lnd/getinfo' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + 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' } }, - NewAddress: async (request: Types.NewAddressRequest): Promise => { + NewAddress: async (request: Types.NewAddressRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/chain/new' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.NewAddressResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - NewInvoice: async (request: Types.NewInvoiceRequest): Promise => { + NewInvoice: async (request: Types.NewInvoiceRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/invoice/new' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.NewInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise => { + NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/product/get/invoice' @@ -694,79 +694,79 @@ export default (params: ClientParams) => ({ finalRoute = finalRoute + (q === '' ? '' : '?' + q) 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.NewInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - OpenChannel: async (request: Types.OpenChannelRequest): Promise => { + OpenChannel: async (request: Types.OpenChannelRequest): Promise => { const auth = await params.retrieveAdminAuth() if (auth === null) throw new Error('retrieveAdminAuth() returned null') let finalRoute = '/api/admin/channel/open' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.OpenChannelResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - PayAddress: async (request: Types.PayAddressRequest): Promise => { + PayAddress: async (request: Types.PayAddressRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/chain/pay' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.PayAddressResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - PayAppUserInvoice: async (request: Types.PayAppUserInvoiceRequest): Promise => { + PayAppUserInvoice: async (request: Types.PayAppUserInvoiceRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/invoice/pay' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.PayInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - PayInvoice: async (request: Types.PayInvoiceRequest): Promise => { + PayInvoice: async (request: Types.PayInvoiceRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/invoice/pay' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.PayInvoiceResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, - RequestNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise => { + RequestNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/npub/token' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.RequestNPubLinkingTokenResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -778,20 +778,20 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/debit/reset' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - ResetNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise => { + ResetNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise => { const auth = await params.retrieveAppAuth() if (auth === null) throw new Error('retrieveAppAuth() returned null') let finalRoute = '/api/app/user/npub/token/reset' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.RequestNPubLinkingTokenResponseValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -803,7 +803,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/debit/finish' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -814,7 +814,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/app/internal/pay' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -825,7 +825,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/app/user/internal/pay' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -836,7 +836,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/app/mock/blance/set' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -847,7 +847,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/app/mock/user/blance/set' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -858,20 +858,20 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/lnd/mock/invoice/paid' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise => { + UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/cb/update' 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') { + if (data.status === 'OK') { const result = data - if (!params.checkResult) return { status: 'OK', ...result } + if(!params.checkResult) return { status: 'OK', ...result } const error = Types.CallbackUrlValidate(result) if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } @@ -883,7 +883,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/admin/channel/policy/update' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -894,7 +894,7 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/user/offer/update' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } @@ -905,19 +905,22 @@ export default (params: ClientParams) => ({ let finalRoute = '/api/guest/invite' 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') { + if (data.status === 'OK') { return data } return { status: 'ERROR', reason: 'invalid response' } }, - UserHealth: async (): Promise => { + UserHealth: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') let finalRoute = '/api/user/health' const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data - if (data.status === 'OK') { - return data + if (data.status === 'OK') { + const result = data + if(!params.checkResult) return { status: 'OK', ...result } + const error = Types.UserHealthStateValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, diff --git a/proto/autogenerated/ts/nostr_client.ts b/proto/autogenerated/ts/nostr_client.ts index ff4eda05..286b6414 100644 --- a/proto/autogenerated/ts/nostr_client.ts +++ b/proto/autogenerated/ts/nostr_client.ts @@ -701,14 +701,17 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ } return { status: 'ERROR', reason: 'invalid response' } }, - UserHealth: async (): Promise => { + UserHealth: async (): Promise => { const auth = await params.retrieveNostrUserAuth() if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') const nostrRequest: NostrRequest = {} const data = await send(params.pubDestination, {rpcName:'UserHealth',authIdentifier:auth, ...nostrRequest }) if (data.status === 'ERROR' && typeof data.reason === 'string') return data if (data.status === 'OK') { - return data + const result = data + if(!params.checkResult) return { status: 'OK', ...result } + const error = Types.UserHealthStateValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } } return { status: 'ERROR', reason: 'invalid response' } }, diff --git a/proto/autogenerated/ts/nostr_transport.ts b/proto/autogenerated/ts/nostr_transport.ts index 99cceb6d..96ad70e8 100644 --- a/proto/autogenerated/ts/nostr_transport.ts +++ b/proto/autogenerated/ts/nostr_transport.ts @@ -494,7 +494,7 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { throw new Error('method not defined: UserHealth') } else { opStats.validate = opStats.guard - await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK' }) + const res = await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK', ...res }) opStats.handle = process.hrtime.bigint() callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } @@ -1114,9 +1114,9 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { stats.guard = process.hrtime.bigint() authCtx = authContext stats.validate = stats.guard - await methods.UserHealth({rpcName:'UserHealth', ctx:authContext }) + const response = await methods.UserHealth({rpcName:'UserHealth', ctx:authContext }) stats.handle = process.hrtime.bigint() - res({status: 'OK'}) + 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 diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index f7a9b021..8609d007 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -274,7 +274,7 @@ export type UseInviteLink_Input = {rpcName:'UseInviteLink', req: UseInviteLinkRe export type UseInviteLink_Output = ResultError | { status: 'OK' } export type UserHealth_Input = {rpcName:'UserHealth'} -export type UserHealth_Output = ResultError | { status: 'OK' } +export type UserHealth_Output = ResultError | ({ status: 'OK' } & UserHealthState) export type ServerMethods = { AddApp?: (req: AddApp_Input & {ctx: AdminContext }) => Promise @@ -347,7 +347,7 @@ export type ServerMethods = { UpdateChannelPolicy?: (req: UpdateChannelPolicy_Input & {ctx: AdminContext }) => Promise UpdateUserOffer?: (req: UpdateUserOffer_Input & {ctx: UserContext }) => Promise UseInviteLink?: (req: UseInviteLink_Input & {ctx: GuestWithPubContext }) => Promise - UserHealth?: (req: UserHealth_Input & {ctx: UserContext }) => Promise + UserHealth?: (req: UserHealth_Input & {ctx: UserContext }) => Promise } export enum AddressType { @@ -3169,6 +3169,24 @@ export const UseInviteLinkRequestValidate = (o?: UseInviteLinkRequest, opts: Use return null } +export type UserHealthState = { + downtime_reason: string +} +export const UserHealthStateOptionalFields: [] = [] +export type UserHealthStateOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + downtime_reason_CustomCheck?: (v: string) => boolean +} +export const UserHealthStateValidate = (o?: UserHealthState, opts: UserHealthStateOptions = {}, path: string = 'UserHealthState::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.downtime_reason !== 'string') return new Error(`${path}.downtime_reason: is not a string`) + if (opts.downtime_reason_CustomCheck && !opts.downtime_reason_CustomCheck(o.downtime_reason)) return new Error(`${path}.downtime_reason: custom check failed`) + + return null +} + export type UserInfo = { balance: number bridge_url: string diff --git a/proto/service/methods.proto b/proto/service/methods.proto index 4ac703ad..9b8e9f42 100644 --- a/proto/service/methods.proto +++ b/proto/service/methods.proto @@ -364,7 +364,7 @@ service LightningPub { // // - rpc UserHealth(structs.Empty)returns(structs.Empty){ + rpc UserHealth(structs.Empty)returns(structs.UserHealthState){ option (auth_type) = "User"; option (http_method) = "post"; option (http_route) = "/api/user/health"; diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 409c1b16..53e8ecf6 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -15,6 +15,10 @@ message EncryptionExchangeRequest { string deviceId = 2; } +message UserHealthState { + string downtime_reason = 1; +} + message UsageMetric { int64 processed_at_ms = 1; int64 parsed_in_nano = 2; diff --git a/src/services/serverMethods/index.ts b/src/services/serverMethods/index.ts index 1b4a2832..5b17a7ee 100644 --- a/src/services/serverMethods/index.ts +++ b/src/services/serverMethods/index.ts @@ -73,7 +73,10 @@ export default (mainHandler: Main): Types.ServerMethods => { if (err != null) throw new Error(err.message) await mainHandler.paymentManager.SetMockInvoiceAsPaid(req) }, - UserHealth: async () => { await mainHandler.lnd.Health() }, + UserHealth: async () => { + try { await mainHandler.lnd.Health(); return { downtime_reason: "" } } + catch (e: any) { return { downtime_reason: e.message } } + }, GetUserInfo: ({ ctx }) => mainHandler.appUserManager.GetUserInfo(ctx), UpdateCallbackUrl: async ({ ctx, req }) => { return mainHandler.appUserManager.UpdateCallbackUrl(ctx, req) From e055444a5ca35a45567362169c98543071ef0624 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Wed, 18 Dec 2024 15:52:47 +0000 Subject: [PATCH 20/35] compress metrics --- proto/autogenerated/client.md | 10 ++++- proto/autogenerated/go/types.go | 10 ++++- proto/autogenerated/ts/types.ts | 73 +++++++++++++++++++++++++----- proto/service/structs.proto | 12 ++++- src/services/metrics/index.ts | 52 +++++++++++++-------- src/services/metrics/tlv.ts | 80 +++++++++++++++++++++++++++++++++ 6 files changed, 205 insertions(+), 32 deletions(-) create mode 100644 src/services/metrics/tlv.ts diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 65c78245..01572a2e 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -866,6 +866,9 @@ The nostr server will send back a message response, and inside the body there wi - __total_fees__: _number_ - __users__: _[UsersInfo](#UsersInfo)_ +### AppUsageMetrics + - __app_metrics__: MAP with key: _string_ and value: _[UsageMetricTlv](#UsageMetricTlv)_ + ### AppUser - __identifier__: _string_ - __info__: _[UserInfo](#UserInfo)_ @@ -1275,6 +1278,7 @@ The nostr server will send back a message response, and inside the body there wi - __update__: _[UpdateChannelPolicyRequest_update](#UpdateChannelPolicyRequest_update)_ ### UsageMetric + - __app_id__: _string_ *this field is optional - __auth_in_nano__: _number_ - __batch__: _boolean_ - __batch_size__: _number_ @@ -1283,10 +1287,14 @@ The nostr server will send back a message response, and inside the body there wi - __parsed_in_nano__: _number_ - __processed_at_ms__: _number_ - __rpc_name__: _string_ + - __success__: _boolean_ - __validate_in_nano__: _number_ +### UsageMetricTlv + - __base_64_tlvs__: ARRAY of: _string_ + ### UsageMetrics - - __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_ + - __apps__: MAP with key: _string_ and value: _[AppUsageMetrics](#AppUsageMetrics)_ ### UseInviteLinkRequest - __invite_token__: _string_ diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index c82ae57f..8d6ba2c2 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -130,6 +130,9 @@ type AppMetrics struct { Total_fees int64 `json:"total_fees"` Users *UsersInfo `json:"users"` } +type AppUsageMetrics struct { + App_metrics map[string]UsageMetricTlv `json:"app_metrics"` +} type AppUser struct { Identifier string `json:"identifier"` Info *UserInfo `json:"info"` @@ -539,6 +542,7 @@ type UpdateChannelPolicyRequest struct { Update *UpdateChannelPolicyRequest_update `json:"update"` } type UsageMetric struct { + App_id string `json:"app_id"` Auth_in_nano int64 `json:"auth_in_nano"` Batch bool `json:"batch"` Batch_size int64 `json:"batch_size"` @@ -547,10 +551,14 @@ type UsageMetric struct { Parsed_in_nano int64 `json:"parsed_in_nano"` Processed_at_ms int64 `json:"processed_at_ms"` Rpc_name string `json:"rpc_name"` + Success bool `json:"success"` Validate_in_nano int64 `json:"validate_in_nano"` } +type UsageMetricTlv struct { + Base_64_tlvs []string `json:"base_64_tlvs"` +} type UsageMetrics struct { - Metrics []UsageMetric `json:"metrics"` + Apps map[string]AppUsageMetrics `json:"apps"` } type UseInviteLinkRequest struct { Invite_token string `json:"invite_token"` diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index 8609d007..824204ad 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -644,6 +644,28 @@ export const AppMetricsValidate = (o?: AppMetrics, opts: AppMetricsOptions = {}, return null } +export type AppUsageMetrics = { + app_metrics: Record +} +export const AppUsageMetricsOptionalFields: [] = [] +export type AppUsageMetricsOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + app_metrics_EntryOptions?: UsageMetricTlvOptions + app_metrics_CustomCheck?: (v: Record) => boolean +} +export const AppUsageMetricsValidate = (o?: AppUsageMetrics, opts: AppUsageMetricsOptions = {}, path: string = 'AppUsageMetrics::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.app_metrics !== 'object' || o.app_metrics === null) return new Error(`${path}.app_metrics: is not an object or is null`) + for (const key in o.app_metrics) { + const app_metricsErr = UsageMetricTlvValidate(o.app_metrics[key], opts.app_metrics_EntryOptions, `${path}.app_metrics['${key}']`) + if (app_metricsErr !== null) return app_metricsErr + } + + return null +} + export type AppUser = { identifier: string info: UserInfo @@ -3071,6 +3093,7 @@ export const UpdateChannelPolicyRequestValidate = (o?: UpdateChannelPolicyReques } export type UsageMetric = { + app_id?: string auth_in_nano: number batch: boolean batch_size: number @@ -3079,11 +3102,14 @@ export type UsageMetric = { parsed_in_nano: number processed_at_ms: number rpc_name: string + success: boolean validate_in_nano: number } -export const UsageMetricOptionalFields: [] = [] +export type UsageMetricOptionalField = 'app_id' +export const UsageMetricOptionalFields: UsageMetricOptionalField[] = ['app_id'] export type UsageMetricOptions = OptionsBaseMessage & { - checkOptionalsAreSet?: [] + checkOptionalsAreSet?: UsageMetricOptionalField[] + app_id_CustomCheck?: (v?: string) => boolean auth_in_nano_CustomCheck?: (v: number) => boolean batch_CustomCheck?: (v: boolean) => boolean batch_size_CustomCheck?: (v: number) => boolean @@ -3092,12 +3118,16 @@ export type UsageMetricOptions = OptionsBaseMessage & { parsed_in_nano_CustomCheck?: (v: number) => boolean processed_at_ms_CustomCheck?: (v: number) => boolean rpc_name_CustomCheck?: (v: string) => boolean + success_CustomCheck?: (v: boolean) => boolean validate_in_nano_CustomCheck?: (v: number) => boolean } export const UsageMetricValidate = (o?: UsageMetric, opts: UsageMetricOptions = {}, path: string = 'UsageMetric::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 ((o.app_id || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('app_id')) && typeof o.app_id !== 'string') return new Error(`${path}.app_id: is not a string`) + if (opts.app_id_CustomCheck && !opts.app_id_CustomCheck(o.app_id)) return new Error(`${path}.app_id: custom check failed`) + if (typeof o.auth_in_nano !== 'number') return new Error(`${path}.auth_in_nano: is not a number`) if (opts.auth_in_nano_CustomCheck && !opts.auth_in_nano_CustomCheck(o.auth_in_nano)) return new Error(`${path}.auth_in_nano: custom check failed`) @@ -3122,31 +3152,54 @@ export const UsageMetricValidate = (o?: UsageMetric, opts: UsageMetricOptions = if (typeof o.rpc_name !== 'string') return new Error(`${path}.rpc_name: is not a string`) if (opts.rpc_name_CustomCheck && !opts.rpc_name_CustomCheck(o.rpc_name)) return new Error(`${path}.rpc_name: custom check failed`) + if (typeof o.success !== 'boolean') return new Error(`${path}.success: is not a boolean`) + if (opts.success_CustomCheck && !opts.success_CustomCheck(o.success)) return new Error(`${path}.success: custom check failed`) + if (typeof o.validate_in_nano !== 'number') return new Error(`${path}.validate_in_nano: is not a number`) if (opts.validate_in_nano_CustomCheck && !opts.validate_in_nano_CustomCheck(o.validate_in_nano)) return new Error(`${path}.validate_in_nano: custom check failed`) return null } +export type UsageMetricTlv = { + base_64_tlvs: string[] +} +export const UsageMetricTlvOptionalFields: [] = [] +export type UsageMetricTlvOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + base_64_tlvs_CustomCheck?: (v: string[]) => boolean +} +export const UsageMetricTlvValidate = (o?: UsageMetricTlv, opts: UsageMetricTlvOptions = {}, path: string = 'UsageMetricTlv::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.base_64_tlvs)) return new Error(`${path}.base_64_tlvs: is not an array`) + for (let index = 0; index < o.base_64_tlvs.length; index++) { + if (typeof o.base_64_tlvs[index] !== 'string') return new Error(`${path}.base_64_tlvs[${index}]: is not a string`) + } + if (opts.base_64_tlvs_CustomCheck && !opts.base_64_tlvs_CustomCheck(o.base_64_tlvs)) return new Error(`${path}.base_64_tlvs: custom check failed`) + + return null +} + export type UsageMetrics = { - metrics: UsageMetric[] + apps: Record } export const UsageMetricsOptionalFields: [] = [] export type UsageMetricsOptions = OptionsBaseMessage & { checkOptionalsAreSet?: [] - metrics_ItemOptions?: UsageMetricOptions - metrics_CustomCheck?: (v: UsageMetric[]) => boolean + apps_EntryOptions?: AppUsageMetricsOptions + apps_CustomCheck?: (v: Record) => boolean } export const UsageMetricsValidate = (o?: UsageMetrics, opts: UsageMetricsOptions = {}, path: string = 'UsageMetrics::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.metrics)) return new Error(`${path}.metrics: is not an array`) - for (let index = 0; index < o.metrics.length; index++) { - const metricsErr = UsageMetricValidate(o.metrics[index], opts.metrics_ItemOptions, `${path}.metrics[${index}]`) - if (metricsErr !== null) return metricsErr + if (typeof o.apps !== 'object' || o.apps === null) return new Error(`${path}.apps: is not an object or is null`) + for (const key in o.apps) { + const appsErr = AppUsageMetricsValidate(o.apps[key], opts.apps_EntryOptions, `${path}.apps['${key}']`) + if (appsErr !== null) return appsErr } - if (opts.metrics_CustomCheck && !opts.metrics_CustomCheck(o.metrics)) return new Error(`${path}.metrics: custom check failed`) return null } diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 53e8ecf6..bece972c 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -29,10 +29,20 @@ message UsageMetric { bool batch = 7; bool nostr = 8; int64 batch_size = 9; + bool success = 10; + optional string app_id = 11; +} + +message UsageMetricTlv { + repeated string base_64_tlvs = 1; +} + +message AppUsageMetrics { + map app_metrics = 1; } message UsageMetrics { - repeated UsageMetric metrics = 1; + map apps = 1; } message AppsMetricsRequest { diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 84b8a355..d0894eef 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -7,14 +7,16 @@ import { BalanceEvent } from '../storage/entity/BalanceEvent.js' import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js' import LND from '../lnd/lnd.js' import HtlcTracker from './htlcTracker.js' +import { encodeTLV, usageMetricsToTlv } from './tlv.js' const maxEvents = 100_000 + export default class Handler { storage: Storage lnd: LND htlcTracker: HtlcTracker - metrics: Types.UsageMetric[] = [] + metrics: Record = {} constructor(storage: Storage, lnd: LND) { this.storage = storage this.lnd = lnd @@ -63,27 +65,39 @@ export default class Handler { } AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) { - const parsed: Types.UsageMetric[] = newMetrics.map(m => ({ - rpc_name: m.rpcName, - batch: m.batch, - nostr: m.nostr, - batch_size: m.batchSize, - parsed_in_nano: Number(m.parse - m.start), - auth_in_nano: Number(m.guard - m.parse), - validate_in_nano: Number(m.validate - m.guard), - handle_in_nano: Number(m.handle - m.validate), - success: !m.error, - app_id: m.app_id ? m.app_id : "", - processed_at_ms: m.startMs - })) - const len = this.metrics.push(...parsed) - if (len > maxEvents) { - this.metrics.splice(0, len - maxEvents) - } + newMetrics.forEach(m => { + const appId = m.app_id || "_root" + const um: Types.UsageMetric = { + rpc_name: m.rpcName, + batch: m.batch, + nostr: m.nostr, + batch_size: m.batchSize, + parsed_in_nano: Number(m.parse - m.start), + auth_in_nano: Number(m.guard - m.parse), + validate_in_nano: Number(m.validate - m.guard), + handle_in_nano: Number(m.handle - m.validate), + success: !m.error, + app_id: m.app_id ? m.app_id : "", + processed_at_ms: m.startMs + } + const tlv = usageMetricsToTlv(um) + const tlvString = Buffer.from(encodeTLV(tlv)).toString("base64") + if (!this.metrics[appId]) { + this.metrics[appId] = { app_metrics: {} } + } + if (!this.metrics[appId].app_metrics[m.rpcName]) { + this.metrics[appId].app_metrics[m.rpcName] = { base_64_tlvs: [] } + } + const len = this.metrics[appId].app_metrics[m.rpcName].base_64_tlvs.push(tlvString) + if (len > maxEvents) { + this.metrics[appId].app_metrics[m.rpcName].base_64_tlvs.splice(0, len - maxEvents) + } + }) } + async GetUsageMetrics(): Promise { return { - metrics: this.metrics + apps: this.metrics } } async GetAppsMetrics(req: Types.AppsMetricsRequest): Promise { diff --git a/src/services/metrics/tlv.ts b/src/services/metrics/tlv.ts new file mode 100644 index 00000000..af377168 --- /dev/null +++ b/src/services/metrics/tlv.ts @@ -0,0 +1,80 @@ +import { bytesToHex, concatBytes } from '@noble/hashes/utils' +import * as Types from '../../../proto/autogenerated/ts/types.js' +export const utf8Decoder: TextDecoder = new TextDecoder('utf-8') +export const utf8Encoder: TextEncoder = new TextEncoder() + +export const usageMetricsToTlv = (metric: Types.UsageMetric): TLV => { + const tlv: TLV = {} + tlv[2] = [integerToUint8Array(metric.processed_at_ms)] // 6 -> 6 + tlv[3] = [integerToUint8Array(metric.parsed_in_nano)] // 6 -> 12 + tlv[4] = [integerToUint8Array(metric.auth_in_nano)] // 6 -> 18 + tlv[5] = [integerToUint8Array(metric.validate_in_nano)] // 6 -> 24 + tlv[6] = [integerToUint8Array(metric.handle_in_nano)] // 6 -> 30 + tlv[7] = [integerToUint8Array(metric.batch_size)] // 6 -> 36 + tlv[8] = [new Uint8Array([metric.batch ? 1 : 0])] // 3 -> 39 + tlv[9] = [new Uint8Array([metric.nostr ? 1 : 0])] // 3 -> 42 + tlv[10] = [new Uint8Array([metric.success ? 1 : 0])] // 3 -> 45 + return tlv +} + +export const tlvToUsageMetrics = (rpcName: string, tlv: TLV): Types.UsageMetric => { + const metric: Types.UsageMetric = { + rpc_name: rpcName, + processed_at_ms: parseInt(bytesToHex(tlv[2][0]), 16), + parsed_in_nano: parseInt(bytesToHex(tlv[3][0]), 16), + auth_in_nano: parseInt(bytesToHex(tlv[4][0]), 16), + validate_in_nano: parseInt(bytesToHex(tlv[5][0]), 16), + handle_in_nano: parseInt(bytesToHex(tlv[6][0]), 16), + batch_size: parseInt(bytesToHex(tlv[7][0]), 16), + batch: tlv[8][0][0] === 1, + nostr: tlv[9][0][0] === 1, + success: tlv[10][0][0] === 1, + } + return metric +} + +export const integerToUint8Array = (number: number): Uint8Array => { + // Create a Uint8Array with enough space to hold a 32-bit integer (4 bytes). + const uint8Array = new Uint8Array(4) + + // Use bitwise operations to extract the bytes. + uint8Array[0] = (number >> 24) & 0xff // Most significant byte (MSB) + uint8Array[1] = (number >> 16) & 0xff + uint8Array[2] = (number >> 8) & 0xff + uint8Array[3] = number & 0xff // Least significant byte (LSB) + + return uint8Array +} + +export type TLV = { [t: number]: Uint8Array[] } +export const parseTLV = (data: Uint8Array): TLV => { + const result: TLV = {} + let rest = data + while (rest.length > 0) { + const t = rest[0] + const l = rest[1] + const v = rest.slice(2, 2 + l) + rest = rest.slice(2 + l) + if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`) + result[t] = result[t] || [] + result[t].push(v) + } + return result +} + +export const encodeTLV = (tlv: TLV): Uint8Array => { + const entries: Uint8Array[] = [] + Object.entries(tlv) + .reverse() + .forEach(([t, vs]) => { + vs.forEach(v => { + const entry = new Uint8Array(v.length + 2) + entry.set([parseInt(t)], 0) + entry.set([v.length], 1) + entry.set(v, 2) + entries.push(entry) + }) + }) + + return concatBytes(...entries) +} \ No newline at end of file From 2334aa8a01ad094e79626bec91d8fc3c05248668 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Wed, 18 Dec 2024 17:07:14 +0000 Subject: [PATCH 21/35] buffer overflow --- src/services/metrics/tlv.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/metrics/tlv.ts b/src/services/metrics/tlv.ts index af377168..cb09c47d 100644 --- a/src/services/metrics/tlv.ts +++ b/src/services/metrics/tlv.ts @@ -5,7 +5,7 @@ export const utf8Encoder: TextEncoder = new TextEncoder() export const usageMetricsToTlv = (metric: Types.UsageMetric): TLV => { const tlv: TLV = {} - tlv[2] = [integerToUint8Array(metric.processed_at_ms)] // 6 -> 6 + tlv[2] = [integerToUint8Array(metric.processed_at_ms / 1000)] // 6 -> 6 tlv[3] = [integerToUint8Array(metric.parsed_in_nano)] // 6 -> 12 tlv[4] = [integerToUint8Array(metric.auth_in_nano)] // 6 -> 18 tlv[5] = [integerToUint8Array(metric.validate_in_nano)] // 6 -> 24 @@ -20,7 +20,7 @@ export const usageMetricsToTlv = (metric: Types.UsageMetric): TLV => { export const tlvToUsageMetrics = (rpcName: string, tlv: TLV): Types.UsageMetric => { const metric: Types.UsageMetric = { rpc_name: rpcName, - processed_at_ms: parseInt(bytesToHex(tlv[2][0]), 16), + processed_at_ms: parseInt(bytesToHex(tlv[2][0]), 16) * 1000, parsed_in_nano: parseInt(bytesToHex(tlv[3][0]), 16), auth_in_nano: parseInt(bytesToHex(tlv[4][0]), 16), validate_in_nano: parseInt(bytesToHex(tlv[5][0]), 16), From 0bf7a37b6cd99ef26d82d01b2490f992e3a6a005 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Wed, 18 Dec 2024 17:32:31 +0000 Subject: [PATCH 22/35] more buffer fix --- src/services/metrics/tlv.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/services/metrics/tlv.ts b/src/services/metrics/tlv.ts index cb09c47d..cf4b2a6e 100644 --- a/src/services/metrics/tlv.ts +++ b/src/services/metrics/tlv.ts @@ -5,11 +5,11 @@ export const utf8Encoder: TextEncoder = new TextEncoder() export const usageMetricsToTlv = (metric: Types.UsageMetric): TLV => { const tlv: TLV = {} - tlv[2] = [integerToUint8Array(metric.processed_at_ms / 1000)] // 6 -> 6 - tlv[3] = [integerToUint8Array(metric.parsed_in_nano)] // 6 -> 12 - tlv[4] = [integerToUint8Array(metric.auth_in_nano)] // 6 -> 18 - tlv[5] = [integerToUint8Array(metric.validate_in_nano)] // 6 -> 24 - tlv[6] = [integerToUint8Array(metric.handle_in_nano)] // 6 -> 30 + tlv[2] = [integerToUint8Array(Math.ceil(metric.processed_at_ms / 1000))] // 6 -> 6 + tlv[3] = [integerToUint8Array(Math.ceil(metric.parsed_in_nano / 1000))] // 6 -> 12 + tlv[4] = [integerToUint8Array(Math.ceil(metric.auth_in_nano / 1000))] // 6 -> 18 + tlv[5] = [integerToUint8Array(Math.ceil(metric.validate_in_nano / 1000))] // 6 -> 24 + tlv[6] = [integerToUint8Array(Math.ceil(metric.handle_in_nano / 1000))] // 6 -> 30 tlv[7] = [integerToUint8Array(metric.batch_size)] // 6 -> 36 tlv[8] = [new Uint8Array([metric.batch ? 1 : 0])] // 3 -> 39 tlv[9] = [new Uint8Array([metric.nostr ? 1 : 0])] // 3 -> 42 @@ -21,10 +21,10 @@ export const tlvToUsageMetrics = (rpcName: string, tlv: TLV): Types.UsageMetric const metric: Types.UsageMetric = { rpc_name: rpcName, processed_at_ms: parseInt(bytesToHex(tlv[2][0]), 16) * 1000, - parsed_in_nano: parseInt(bytesToHex(tlv[3][0]), 16), - auth_in_nano: parseInt(bytesToHex(tlv[4][0]), 16), - validate_in_nano: parseInt(bytesToHex(tlv[5][0]), 16), - handle_in_nano: parseInt(bytesToHex(tlv[6][0]), 16), + parsed_in_nano: parseInt(bytesToHex(tlv[3][0]), 16) * 1000, + auth_in_nano: parseInt(bytesToHex(tlv[4][0]), 16) * 1000, + validate_in_nano: parseInt(bytesToHex(tlv[5][0]), 16) * 1000, + handle_in_nano: parseInt(bytesToHex(tlv[6][0]), 16) * 1000, batch_size: parseInt(bytesToHex(tlv[7][0]), 16), batch: tlv[8][0][0] === 1, nostr: tlv[9][0][0] === 1, From 6a22e3439a2cff708f93267c5c8076875dfdd5d9 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 16:03:27 +0000 Subject: [PATCH 23/35] persisted metrics --- proto/autogenerated/client.md | 2 + proto/autogenerated/go/types.go | 4 +- proto/autogenerated/ts/types.ts | 13 ++ proto/service/structs.proto | 3 + src/services/{metrics => helpers}/tlv.ts | 10 ++ src/services/metrics/index.ts | 60 +++++--- src/services/storage/index.ts | 3 + src/services/storage/metricsEventStorage.ts | 156 ++++++++++++++++++++ src/services/storage/stateBundler.ts | 4 +- 9 files changed, 230 insertions(+), 25 deletions(-) rename src/services/{metrics => helpers}/tlv.ts (94%) create mode 100644 src/services/storage/metricsEventStorage.ts diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 01572a2e..9b709dda 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -1291,7 +1291,9 @@ The nostr server will send back a message response, and inside the body there wi - __validate_in_nano__: _number_ ### UsageMetricTlv + - __available_chunks__: ARRAY of: _number_ - __base_64_tlvs__: ARRAY of: _string_ + - __current_chunk__: _number_ ### UsageMetrics - __apps__: MAP with key: _string_ and value: _[AppUsageMetrics](#AppUsageMetrics)_ diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 8d6ba2c2..a316cbdc 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -555,7 +555,9 @@ type UsageMetric struct { Validate_in_nano int64 `json:"validate_in_nano"` } type UsageMetricTlv struct { - Base_64_tlvs []string `json:"base_64_tlvs"` + Available_chunks []int64 `json:"available_chunks"` + Base_64_tlvs []string `json:"base_64_tlvs"` + Current_chunk int64 `json:"current_chunk"` } type UsageMetrics struct { Apps map[string]AppUsageMetrics `json:"apps"` diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index 824204ad..6b815ab7 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -3162,23 +3162,36 @@ export const UsageMetricValidate = (o?: UsageMetric, opts: UsageMetricOptions = } export type UsageMetricTlv = { + available_chunks: number[] base_64_tlvs: string[] + current_chunk: number } export const UsageMetricTlvOptionalFields: [] = [] export type UsageMetricTlvOptions = OptionsBaseMessage & { checkOptionalsAreSet?: [] + available_chunks_CustomCheck?: (v: number[]) => boolean base_64_tlvs_CustomCheck?: (v: string[]) => boolean + current_chunk_CustomCheck?: (v: number) => boolean } export const UsageMetricTlvValidate = (o?: UsageMetricTlv, opts: UsageMetricTlvOptions = {}, path: string = 'UsageMetricTlv::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.available_chunks)) return new Error(`${path}.available_chunks: is not an array`) + for (let index = 0; index < o.available_chunks.length; index++) { + if (typeof o.available_chunks[index] !== 'number') return new Error(`${path}.available_chunks[${index}]: is not a number`) + } + if (opts.available_chunks_CustomCheck && !opts.available_chunks_CustomCheck(o.available_chunks)) return new Error(`${path}.available_chunks: custom check failed`) + if (!Array.isArray(o.base_64_tlvs)) return new Error(`${path}.base_64_tlvs: is not an array`) for (let index = 0; index < o.base_64_tlvs.length; index++) { if (typeof o.base_64_tlvs[index] !== 'string') return new Error(`${path}.base_64_tlvs[${index}]: is not a string`) } if (opts.base_64_tlvs_CustomCheck && !opts.base_64_tlvs_CustomCheck(o.base_64_tlvs)) return new Error(`${path}.base_64_tlvs: custom check failed`) + if (typeof o.current_chunk !== 'number') return new Error(`${path}.current_chunk: is not a number`) + if (opts.current_chunk_CustomCheck && !opts.current_chunk_CustomCheck(o.current_chunk)) return new Error(`${path}.current_chunk: custom check failed`) + return null } diff --git a/proto/service/structs.proto b/proto/service/structs.proto index bece972c..572f224f 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -35,6 +35,9 @@ message UsageMetric { message UsageMetricTlv { repeated string base_64_tlvs = 1; + int64 current_chunk = 2; + repeated int64 available_chunks = 3; + } message AppUsageMetrics { diff --git a/src/services/metrics/tlv.ts b/src/services/helpers/tlv.ts similarity index 94% rename from src/services/metrics/tlv.ts rename to src/services/helpers/tlv.ts index cf4b2a6e..7e8d6b08 100644 --- a/src/services/metrics/tlv.ts +++ b/src/services/helpers/tlv.ts @@ -3,6 +3,16 @@ import * as Types from '../../../proto/autogenerated/ts/types.js' export const utf8Decoder: TextDecoder = new TextDecoder('utf-8') export const utf8Encoder: TextEncoder = new TextEncoder() +export const encodeListTLV = (list: Uint8Array[]): TLV => { + const tlv: TLV = {} + tlv[64] = list + return tlv +} + +export const decodeListTLV = (tlv: TLV): Uint8Array[] => { + return tlv[64] +} + export const usageMetricsToTlv = (metric: Types.UsageMetric): TLV => { const tlv: TLV = {} tlv[2] = [integerToUint8Array(Math.ceil(metric.processed_at_ms / 1000))] // 6 -> 6 diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index d0894eef..3ee77898 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -1,3 +1,4 @@ +import fs from 'fs' import Storage from '../storage/index.js' import * as Types from '../../../proto/autogenerated/ts/types.js' import { Application } from '../storage/entity/Application.js' @@ -7,22 +8,28 @@ import { BalanceEvent } from '../storage/entity/BalanceEvent.js' import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js' import LND from '../lnd/lnd.js' import HtlcTracker from './htlcTracker.js' -import { encodeTLV, usageMetricsToTlv } from './tlv.js' -const maxEvents = 100_000 +import { MainSettings } from '../main/settings.js' +import { getLogger } from '../helpers/logger.js' +import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.js' + export default class Handler { - storage: Storage lnd: LND htlcTracker: HtlcTracker - metrics: Record = {} - constructor(storage: Storage, lnd: LND) { + metricsPath: string + logger = getLogger({ component: "metrics" }) + constructor(mainSettings: MainSettings, storage: Storage, lnd: LND) { this.storage = storage this.lnd = lnd this.htlcTracker = new HtlcTracker(this.storage) + this.metricsPath = [mainSettings.storageSettings.dataDir, "metrics"].join("/") + } + + async HtlcCb(htlc: HtlcEvent) { await this.htlcTracker.onHtlcEvent(htlc) } @@ -31,7 +38,6 @@ export default class Handler { const providers = await this.storage.liquidityStorage.GetTrackedProviders() const channels = await this.lnd.GetChannelBalance() let providerTotal = 0 - console.log({ providers }) providers.forEach(p => { if (p.provider_type === 'lnPub') { providerTotal += p.latest_balance @@ -64,6 +70,12 @@ export default class Handler { })) } + async GetUsageMetrics(): Promise { + return this.storage.metricsEventStorage.LoadLatestMetrics() + } + + + AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) { newMetrics.forEach(m => { const appId = m.app_id || "_root" @@ -81,25 +93,29 @@ export default class Handler { processed_at_ms: m.startMs } const tlv = usageMetricsToTlv(um) - const tlvString = Buffer.from(encodeTLV(tlv)).toString("base64") - if (!this.metrics[appId]) { - this.metrics[appId] = { app_metrics: {} } - } - if (!this.metrics[appId].app_metrics[m.rpcName]) { - this.metrics[appId].app_metrics[m.rpcName] = { base_64_tlvs: [] } - } - const len = this.metrics[appId].app_metrics[m.rpcName].base_64_tlvs.push(tlvString) - if (len > maxEvents) { - this.metrics[appId].app_metrics[m.rpcName].base_64_tlvs.splice(0, len - maxEvents) - } + this.storage.metricsEventStorage.AddMetricEvent(appId, m.rpcName, encodeTLV(tlv)) }) } - async GetUsageMetrics(): Promise { - return { - apps: this.metrics - } - } + + + /* addTrackedMetric = (appId: string, method: string, metric: Uint8Array) => { + if (!this.metaReady) { + throw new Error("meta metrics not ready") + } + const tlvString = Buffer.from(metric).toString("base64") + if (!this.metrics[appId]) { + this.metrics[appId] = { app_metrics: {} } + } + if (!this.metrics[appId].app_metrics[method]) { + this.metrics[appId].app_metrics[method] = { base_64_tlvs: [] } + } + const len = this.metrics[appId].app_metrics[method].base_64_tlvs.push(tlvString) + if (len > maxEvents) { + this.metrics[appId].app_metrics[method].base_64_tlvs.splice(0, len - maxEvents) + } + } */ + async GetAppsMetrics(req: Types.AppsMetricsRequest): Promise { const dbApps = await this.storage.applicationStorage.GetApplications() const apps = await Promise.all(dbApps.map(app => this.GetAppMetrics(req, app))) diff --git a/src/services/storage/index.ts b/src/services/storage/index.ts index f266fd01..7f105ce0 100644 --- a/src/services/storage/index.ts +++ b/src/services/storage/index.ts @@ -6,6 +6,7 @@ import ApplicationStorage from './applicationStorage.js' import UserStorage from "./userStorage.js"; import PaymentStorage from "./paymentStorage.js"; import MetricsStorage from "./metricsStorage.js"; +import MetricsEventStorage from "./metricsEventStorage.js"; import TransactionsQueue, { TX } from "./transactionsQueue.js"; import EventsLogManager from "./eventsLog.js"; import { LiquidityStorage } from "./liquidityStorage.js"; @@ -29,6 +30,7 @@ export default class { userStorage: UserStorage paymentStorage: PaymentStorage metricsStorage: MetricsStorage + metricsEventStorage: MetricsEventStorage liquidityStorage: LiquidityStorage debitStorage: DebitStorage offerStorage: OfferStorage @@ -47,6 +49,7 @@ export default class { this.applicationStorage = new ApplicationStorage(this.DB, this.userStorage, this.txQueue) this.paymentStorage = new PaymentStorage(this.DB, this.userStorage, this.txQueue) this.metricsStorage = new MetricsStorage(this.settings) + this.metricsEventStorage = new MetricsEventStorage(this.settings) this.liquidityStorage = new LiquidityStorage(this.DB, this.txQueue) this.debitStorage = new DebitStorage(this.DB, this.txQueue) this.offerStorage = new OfferStorage(this.DB, this.txQueue) diff --git a/src/services/storage/metricsEventStorage.ts b/src/services/storage/metricsEventStorage.ts new file mode 100644 index 00000000..6bf436e5 --- /dev/null +++ b/src/services/storage/metricsEventStorage.ts @@ -0,0 +1,156 @@ +import fs from 'fs' +import * as Types from '../../../proto/autogenerated/ts/types.js' +import { StorageSettings } from "./index.js"; +import { decodeListTLV, encodeListTLV, encodeTLV, parseTLV } from '../helpers/tlv.js'; +const chunkSizeBytes = 128 * 1024 +export default class { + settings: StorageSettings + metricsPath: string + metaReady = false + metricsMeta: Record> = {} + pendingMetrics: Record> = {} + last24hOk: Record = {} + last24hFail: Record = {} + lastPersisted: number = 0 + constructor(settings: StorageSettings) { + this.settings = settings; + this.metricsPath = [settings.dataDir, "metric_events"].join("/") + this.initMetricsMeta() + setInterval(() => { + if (Date.now() - this.lastPersisted > 1000 * 60 * 5) { + this.persistMetrics() + } + }, 1000 * 60 * 5) + process.on('exit', () => { + this.persistMetrics() + }); + + // catch ctrl+c event and exit normally + process.on('SIGINT', () => { + console.log('Ctrl-C...'); + process.exit(2); + }); + + //catch uncaught exceptions, trace, then exit normally + process.on('uncaughtException', (e) => { + console.log('Uncaught Exception...'); + console.log(e.stack); + process.exit(99); + }); + } + + AddMetricEvent = (appId: string, method: string, metric: Uint8Array) => { + if (!this.metaReady) { + throw new Error("meta metrics not ready") + } + if (!this.pendingMetrics[appId]) { + this.pendingMetrics[appId] = {} + } + if (!this.pendingMetrics[appId][method]) { + this.pendingMetrics[appId][method] = { tlvs: [] } + } + this.pendingMetrics[appId][method].tlvs.push(metric) + } + + LoadLatestMetrics = async (): Promise => { + this.persistMetrics() + const metrics: Types.UsageMetrics = { apps: {} } + this.foreachMetricMethodFile((app, method, tlvFiles) => { + if (tlvFiles.length === 0) { return } + const methodPath = [this.metricsPath, app, method].join("/") + const latest = tlvFiles[tlvFiles.length - 1] + const tlvFile = [methodPath, `${latest}.mtlv`].join("/") + const tlv = fs.readFileSync(tlvFile) + const decoded = decodeListTLV(parseTLV(tlv)) + if (!metrics.apps[app]) { + metrics.apps[app] = { app_metrics: {} } + } + metrics.apps[app].app_metrics[method] = { + base_64_tlvs: decoded.map(d => Buffer.from(d).toString('base64')), + current_chunk: latest, + available_chunks: tlvFiles + } + }) + return metrics + } + + persistMetrics = () => { + if (!this.metaReady) { + throw new Error("meta metrics not ready") + } + this.lastPersisted = Date.now() + const tosync = this.pendingMetrics + this.pendingMetrics = {} + const apps = Object.keys(tosync) + apps.map(app => { + const appPath = [this.metricsPath, app].join("/") + if (!fs.existsSync(appPath)) { + fs.mkdirSync(appPath, { recursive: true }); + } + const methods = Object.keys(tosync[app]) + methods.map(methodName => { + const methodPath = [appPath, methodName].join("/") + if (!fs.existsSync(methodPath)) { + fs.mkdirSync(methodPath, { recursive: true }); + } + const method = tosync[app][methodName] + const meta = this.getMetricsMeta(app, methodName) + const chunks = meta.chunks.length > 0 ? meta.chunks : [0] + const latest = chunks[chunks.length - 1] + const tlv = encodeTLV(encodeListTLV(method.tlvs)) + const tlvFile = [methodPath, `${latest}.mtlv`].join("/") + fs.appendFileSync(tlvFile, Buffer.from(tlv)) + if (fs.lstatSync(tlvFile).size > chunkSizeBytes) { + this.updateMetricsMeta(app, methodName, [...chunks, latest + 1]) + } + }) + }) + } + + initMetricsMeta = () => { + this.foreachMetricMethodFile((app, method, tlvFiles) => { + this.updateMetricsMeta(app, method, tlvFiles) + }) + this.metaReady = true + } + + updateMetricsMeta = (appId: string, method: string, sortedChunks: number[]) => { + if (!this.metricsMeta[appId]) { + this.metricsMeta[appId] = {} + } + this.metricsMeta[appId][method] = { chunks: sortedChunks } + } + + getMetricsMeta = (appId: string, method: string) => { + if (!this.metricsMeta[appId]) { + return { chunks: [] } + } + return this.metricsMeta[appId][method] || { chunks: [] } + } + + foreachMetricMethodFile = (cb: (appId: string, method: string, tlvFiles: number[]) => void) => { + if (!fs.existsSync(this.metricsPath)) { + fs.mkdirSync(this.metricsPath, { recursive: true }); + } + const apps = fs.readdirSync(this.metricsPath) + apps.forEach(appDir => { + const appPath = [this.metricsPath, appDir].join("/") + if (!fs.lstatSync(appPath).isDirectory()) { + return + } + const methods = fs.readdirSync(appPath) + methods.forEach(methodDir => { + const methodPath = [appPath, methodDir].join("/") + if (!fs.lstatSync(methodPath).isDirectory()) { + return + } + const tlvFiles = fs.readdirSync(methodPath) + .filter(f => f.endsWith(".mtlv")) + .map(f => +f.slice(0, -".mtlv".length)) + .filter(n => !isNaN(n)) + .sort((a, b) => a - b) + cb(appDir, methodDir, tlvFiles) + }) + }) + } +} \ No newline at end of file diff --git a/src/services/storage/stateBundler.ts b/src/services/storage/stateBundler.ts index dab2a215..4cd000be 100644 --- a/src/services/storage/stateBundler.ts +++ b/src/services/storage/stateBundler.ts @@ -34,7 +34,7 @@ export class StateBundler { latestReport = Date.now() reportLog = getLogger({ component: 'stateBundlerReport' }) constructor() { - process.on('exit', () => { + /* process.on('exit', () => { this.Report() }); @@ -49,7 +49,7 @@ export class StateBundler { console.log('Uncaught Exception...'); console.log(e.stack); process.exit(99); - }); + }); */ } increment = (key: string, value: number) => { From 4aeb565596bad486ed69f92db887d849d745bb8c Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 16:04:57 +0000 Subject: [PATCH 24/35] fix --- src/services/metrics/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 3ee77898..943943b9 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -18,13 +18,11 @@ export default class Handler { storage: Storage lnd: LND htlcTracker: HtlcTracker - metricsPath: string logger = getLogger({ component: "metrics" }) - constructor(mainSettings: MainSettings, storage: Storage, lnd: LND) { + constructor(storage: Storage, lnd: LND) { this.storage = storage this.lnd = lnd this.htlcTracker = new HtlcTracker(this.storage) - this.metricsPath = [mainSettings.storageSettings.dataDir, "metrics"].join("/") } From 16d419836490097d5f869c73109b038596bcfcf2 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 17:42:54 +0000 Subject: [PATCH 25/35] error rates --- proto/autogenerated/client.md | 24 +++++++ proto/autogenerated/go/http_client.go | 27 +++++++ proto/autogenerated/go/types.go | 12 ++++ proto/autogenerated/ts/express_server.ts | 19 +++++ proto/autogenerated/ts/http_client.ts | 14 ++++ proto/autogenerated/ts/nostr_client.ts | 14 ++++ proto/autogenerated/ts/nostr_transport.ts | 13 ++++ proto/autogenerated/ts/types.ts | 79 ++++++++++++++++++++- proto/service/methods.proto | 6 ++ proto/service/structs.proto | 14 ++++ src/services/metrics/index.ts | 44 +++++++++++- src/services/serverMethods/index.ts | 3 + src/services/storage/metricsEventStorage.ts | 63 ++++++++++++++-- 13 files changed, 323 insertions(+), 9 deletions(-) diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 9b709dda..d6a05091 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -98,6 +98,11 @@ The nostr server will send back a message response, and inside the body there wi - This methods has an __empty__ __request__ body - output: [DebitAuthorizations](#DebitAuthorizations) +- GetErrorStats + - auth type: __Metrics__ + - This methods has an __empty__ __request__ body + - output: [ErrorStats](#ErrorStats) + - GetHttpCreds - auth type: __User__ - This methods has an __empty__ __request__ body @@ -467,6 +472,13 @@ The nostr server will send back a message response, and inside the body there wi - This methods has an __empty__ __request__ body - output: [DebitAuthorizations](#DebitAuthorizations) +- GetErrorStats + - auth type: __Metrics__ + - http method: __post__ + - http route: __/api/reports/errors__ + - This methods has an __empty__ __request__ body + - output: [ErrorStats](#ErrorStats) + - GetHttpCreds - auth type: __User__ - http method: __post__ @@ -986,6 +998,18 @@ The nostr server will send back a message response, and inside the body there wi ### EnrollAdminTokenRequest - __admin_token__: _string_ +### ErrorStat + - __errors__: _number_ + - __from_unix__: _number_ + - __total__: _number_ + +### ErrorStats + - __past10m__: _[ErrorStat](#ErrorStat)_ + - __past1h__: _[ErrorStat](#ErrorStat)_ + - __past1m__: _[ErrorStat](#ErrorStat)_ + - __past24h__: _[ErrorStat](#ErrorStat)_ + - __past6h__: _[ErrorStat](#ErrorStat)_ + ### FrequencyRule - __amount__: _number_ - __interval__: _[IntervalType](#IntervalType)_ diff --git a/proto/autogenerated/go/http_client.go b/proto/autogenerated/go/http_client.go index 522df237..5ecc8996 100644 --- a/proto/autogenerated/go/http_client.go +++ b/proto/autogenerated/go/http_client.go @@ -78,6 +78,7 @@ type Client struct { GetAppUserLNURLInfo func(req GetAppUserLNURLInfoRequest) (*LnurlPayInfoResponse, error) GetAppsMetrics func(req AppsMetricsRequest) (*AppsMetrics, error) GetDebitAuthorizations func() (*DebitAuthorizations, error) + GetErrorStats func() (*ErrorStats, error) GetHttpCreds func() (*HttpCreds, error) GetInviteLinkState func(req GetInviteTokenStateRequest) (*GetInviteTokenStateResponse, error) GetLNURLChannelLink func() (*LnurlLinkResponse, error) @@ -758,6 +759,32 @@ func NewClient(params ClientParams) *Client { } return &res, nil }, + GetErrorStats: func() (*ErrorStats, error) { + auth, err := params.RetrieveMetricsAuth() + if err != nil { + return nil, err + } + finalRoute := "/api/reports/errors" + body := []byte{} + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return nil, err + } + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := ErrorStats{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, // server streaming method: GetHttpCreds not implemented GetInviteLinkState: func(req GetInviteTokenStateRequest) (*GetInviteTokenStateResponse, error) { auth, err := params.RetrieveAdminAuth() diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index a316cbdc..2fda35f5 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -250,6 +250,18 @@ type EncryptionExchangeRequest struct { type EnrollAdminTokenRequest struct { Admin_token string `json:"admin_token"` } +type ErrorStat struct { + Errors int64 `json:"errors"` + From_unix int64 `json:"from_unix"` + Total int64 `json:"total"` +} +type ErrorStats struct { + Past10m *ErrorStat `json:"past10m"` + Past1h *ErrorStat `json:"past1h"` + Past1m *ErrorStat `json:"past1m"` + Past24h *ErrorStat `json:"past24h"` + Past6h *ErrorStat `json:"past6h"` +} type FrequencyRule struct { Amount int64 `json:"amount"` Interval IntervalType `json:"interval"` diff --git a/proto/autogenerated/ts/express_server.ts b/proto/autogenerated/ts/express_server.ts index f76044ea..9b1fd1ea 100644 --- a/proto/autogenerated/ts/express_server.ts +++ b/proto/autogenerated/ts/express_server.ts @@ -885,6 +885,25 @@ 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.GetErrorStats) throw new Error('method: GetErrorStats is not implemented') + app.post('/api/reports/errors', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'GetErrorStats', 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.GetErrorStats) throw new Error('method: GetErrorStats is not implemented') + const authContext = await opts.MetricsAuthGuard(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.GetErrorStats({rpcName:'GetErrorStats', 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.GetInviteLinkState) throw new Error('method: GetInviteLinkState is not implemented') app.post('/api/admin/app/invite/get', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'GetInviteLinkState', batch: false, nostr: false, batchSize: 0} diff --git a/proto/autogenerated/ts/http_client.ts b/proto/autogenerated/ts/http_client.ts index 0097d29e..10ae0041 100644 --- a/proto/autogenerated/ts/http_client.ts +++ b/proto/autogenerated/ts/http_client.ts @@ -332,6 +332,20 @@ export default (params: ClientParams) => ({ } return { status: 'ERROR', reason: 'invalid response' } }, + GetErrorStats: async (): Promise => { + const auth = await params.retrieveMetricsAuth() + if (auth === null) throw new Error('retrieveMetricsAuth() returned null') + let finalRoute = '/api/reports/errors' + const { data } = await axios.post(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.ErrorStatsValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetHttpCreds: async (cb: (v:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise => { throw new Error('http streams are not supported')}, GetInviteLinkState: async (request: Types.GetInviteTokenStateRequest): Promise => { const auth = await params.retrieveAdminAuth() diff --git a/proto/autogenerated/ts/nostr_client.ts b/proto/autogenerated/ts/nostr_client.ts index 286b6414..04f14158 100644 --- a/proto/autogenerated/ts/nostr_client.ts +++ b/proto/autogenerated/ts/nostr_client.ts @@ -247,6 +247,20 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ } return { status: 'ERROR', reason: 'invalid response' } }, + GetErrorStats: async (): Promise => { + const auth = await params.retrieveNostrMetricsAuth() + if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null') + const nostrRequest: NostrRequest = {} + const data = await send(params.pubDestination, {rpcName:'GetErrorStats',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.ErrorStatsValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetHttpCreds: async (cb: (res:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise => { const auth = await params.retrieveNostrUserAuth() if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') diff --git a/proto/autogenerated/ts/nostr_transport.ts b/proto/autogenerated/ts/nostr_transport.ts index 96ad70e8..7d3f2c7d 100644 --- a/proto/autogenerated/ts/nostr_transport.ts +++ b/proto/autogenerated/ts/nostr_transport.ts @@ -634,6 +634,19 @@ 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 'GetErrorStats': + try { + if (!methods.GetErrorStats) throw new Error('method: GetErrorStats 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.GetErrorStats({rpcName:'GetErrorStats', 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 'GetHttpCreds': try { if (!methods.GetHttpCreds) throw new Error('method: GetHttpCreds is not implemented') diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index 6b815ab7..7fbffbf1 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -27,8 +27,8 @@ export type GuestWithPubMethodOutputs = LinkNPubThroughToken_Output | UseInviteL export type MetricsContext = { operator_id: string } -export type MetricsMethodInputs = GetAppsMetrics_Input | GetLndMetrics_Input | GetUsageMetrics_Input -export type MetricsMethodOutputs = GetAppsMetrics_Output | GetLndMetrics_Output | GetUsageMetrics_Output +export type MetricsMethodInputs = GetAppsMetrics_Input | GetErrorStats_Input | GetLndMetrics_Input | GetUsageMetrics_Input +export type MetricsMethodOutputs = GetAppsMetrics_Output | GetErrorStats_Output | GetLndMetrics_Output | GetUsageMetrics_Output export type UserContext = { app_id: string app_user_id: string @@ -110,6 +110,9 @@ export type GetAppsMetrics_Output = ResultError | ({ status: 'OK' } & AppsMetric export type GetDebitAuthorizations_Input = {rpcName:'GetDebitAuthorizations'} export type GetDebitAuthorizations_Output = ResultError | ({ status: 'OK' } & DebitAuthorizations) +export type GetErrorStats_Input = {rpcName:'GetErrorStats'} +export type GetErrorStats_Output = ResultError | ({ status: 'OK' } & ErrorStats) + export type GetHttpCreds_Input = {rpcName:'GetHttpCreds', cb:(res: HttpCreds, err:Error|null)=> void} export type GetHttpCreds_Output = ResultError | { status: 'OK' } @@ -300,6 +303,7 @@ export type ServerMethods = { GetAppUserLNURLInfo?: (req: GetAppUserLNURLInfo_Input & {ctx: AppContext }) => Promise GetAppsMetrics?: (req: GetAppsMetrics_Input & {ctx: MetricsContext }) => Promise GetDebitAuthorizations?: (req: GetDebitAuthorizations_Input & {ctx: UserContext }) => Promise + GetErrorStats?: (req: GetErrorStats_Input & {ctx: MetricsContext }) => Promise GetHttpCreds?: (req: GetHttpCreds_Input & {ctx: UserContext }) => Promise GetInviteLinkState?: (req: GetInviteLinkState_Input & {ctx: AdminContext }) => Promise GetLNURLChannelLink?: (req: GetLNURLChannelLink_Input & {ctx: UserContext }) => Promise @@ -1371,6 +1375,77 @@ export const EnrollAdminTokenRequestValidate = (o?: EnrollAdminTokenRequest, opt return null } +export type ErrorStat = { + errors: number + from_unix: number + total: number +} +export const ErrorStatOptionalFields: [] = [] +export type ErrorStatOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + errors_CustomCheck?: (v: number) => boolean + from_unix_CustomCheck?: (v: number) => boolean + total_CustomCheck?: (v: number) => boolean +} +export const ErrorStatValidate = (o?: ErrorStat, opts: ErrorStatOptions = {}, path: string = 'ErrorStat::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.errors !== 'number') return new Error(`${path}.errors: is not a number`) + if (opts.errors_CustomCheck && !opts.errors_CustomCheck(o.errors)) return new Error(`${path}.errors: custom check failed`) + + if (typeof o.from_unix !== 'number') return new Error(`${path}.from_unix: is not a number`) + if (opts.from_unix_CustomCheck && !opts.from_unix_CustomCheck(o.from_unix)) return new Error(`${path}.from_unix: custom check failed`) + + if (typeof o.total !== 'number') return new Error(`${path}.total: is not a number`) + if (opts.total_CustomCheck && !opts.total_CustomCheck(o.total)) return new Error(`${path}.total: custom check failed`) + + return null +} + +export type ErrorStats = { + past10m: ErrorStat + past1h: ErrorStat + past1m: ErrorStat + past24h: ErrorStat + past6h: ErrorStat +} +export const ErrorStatsOptionalFields: [] = [] +export type ErrorStatsOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + past10m_Options?: ErrorStatOptions + past1h_Options?: ErrorStatOptions + past1m_Options?: ErrorStatOptions + past24h_Options?: ErrorStatOptions + past6h_Options?: ErrorStatOptions +} +export const ErrorStatsValidate = (o?: ErrorStats, opts: ErrorStatsOptions = {}, path: string = 'ErrorStats::root.'): Error | null => { + if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + + const past10mErr = ErrorStatValidate(o.past10m, opts.past10m_Options, `${path}.past10m`) + if (past10mErr !== null) return past10mErr + + + const past1hErr = ErrorStatValidate(o.past1h, opts.past1h_Options, `${path}.past1h`) + if (past1hErr !== null) return past1hErr + + + const past1mErr = ErrorStatValidate(o.past1m, opts.past1m_Options, `${path}.past1m`) + if (past1mErr !== null) return past1mErr + + + const past24hErr = ErrorStatValidate(o.past24h, opts.past24h_Options, `${path}.past24h`) + if (past24hErr !== null) return past24hErr + + + const past6hErr = ErrorStatValidate(o.past6h, opts.past6h_Options, `${path}.past6h`) + if (past6hErr !== null) return past6hErr + + + return null +} + export type FrequencyRule = { amount: number interval: IntervalType diff --git a/proto/service/methods.proto b/proto/service/methods.proto index 9b8e9f42..f628120e 100644 --- a/proto/service/methods.proto +++ b/proto/service/methods.proto @@ -178,6 +178,12 @@ service LightningPub { option (http_route) = "/api/reports/usage"; option (nostr) = true; } + rpc GetErrorStats(structs.Empty) returns (structs.ErrorStats) { + option (auth_type) = "Metrics"; + option (http_method) = "post"; + option (http_route) = "/api/reports/errors"; + option (nostr) = true; + } rpc GetAppsMetrics(structs.AppsMetricsRequest) returns (structs.AppsMetrics) { option (auth_type) = "Metrics"; diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 572f224f..94556216 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -19,6 +19,20 @@ message UserHealthState { string downtime_reason = 1; } +message ErrorStat { + int64 from_unix = 1; + int64 total = 2; + int64 errors = 3; +} + +message ErrorStats { + ErrorStat past24h = 1; + ErrorStat past6h = 2; + ErrorStat past1h = 3; + ErrorStat past10m = 4; + ErrorStat past1m = 5; +} + message UsageMetric { int64 processed_at_ms = 1; int64 parsed_in_nano = 2; diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 943943b9..bcfcbc58 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -72,6 +72,48 @@ export default class Handler { return this.storage.metricsEventStorage.LoadLatestMetrics() } + async GetErrorStats(): Promise { + const last24h = this.storage.metricsEventStorage.getlast24hCache() + const nowUnix = Math.floor(Date.now() / 1000) + const stats: Types.ErrorStats = { + past24h: { errors: 0, total: 0, from_unix: nowUnix - 60 * 60 * 24 }, + past6h: { errors: 0, total: 0, from_unix: nowUnix - 60 * 60 * 6 }, + past1h: { errors: 0, total: 0, from_unix: nowUnix - 60 * 60 }, + past10m: { errors: 0, total: 0, from_unix: nowUnix - 60 * 10 }, + past1m: { errors: 0, total: 0, from_unix: nowUnix - 60 }, + } + for (let i = last24h.length; i >= 0; i--) { + const e = last24h[i] + if (e.ts < stats.past24h.from_unix) { + break + } + + stats.past24h.total += e.ok + e.fail + stats.past24h.errors += e.fail + + if (e.ts >= stats.past6h.from_unix) { + stats.past6h.total += e.ok + e.fail + stats.past6h.errors += e.fail + } + + if (e.ts >= stats.past1h.from_unix) { + stats.past1h.total += e.ok + e.fail + stats.past1h.errors += e.fail + } + + if (e.ts >= stats.past10m.from_unix) { + stats.past10m.total += e.ok + e.fail + stats.past10m.errors += e.fail + } + + if (e.ts >= stats.past1m.from_unix) { + stats.past1m.total += e.ok + e.fail + stats.past1m.errors += e.fail + } + } + return stats + } + AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) { @@ -91,7 +133,7 @@ export default class Handler { processed_at_ms: m.startMs } const tlv = usageMetricsToTlv(um) - this.storage.metricsEventStorage.AddMetricEvent(appId, m.rpcName, encodeTLV(tlv)) + this.storage.metricsEventStorage.AddMetricEvent(appId, m.rpcName, encodeTLV(tlv), !m.error) }) } diff --git a/src/services/serverMethods/index.ts b/src/services/serverMethods/index.ts index 5b17a7ee..316fbf92 100644 --- a/src/services/serverMethods/index.ts +++ b/src/services/serverMethods/index.ts @@ -7,6 +7,9 @@ export default (mainHandler: Main): Types.ServerMethods => { GetUsageMetrics: async ({ ctx }) => { return mainHandler.metricsManager.GetUsageMetrics() }, + GetErrorStats: async ({ ctx }) => { + return mainHandler.metricsManager.GetErrorStats() + }, GetAppsMetrics: async ({ ctx, req }) => { return mainHandler.metricsManager.GetAppsMetrics(req) }, diff --git a/src/services/storage/metricsEventStorage.ts b/src/services/storage/metricsEventStorage.ts index 6bf436e5..fc7017d5 100644 --- a/src/services/storage/metricsEventStorage.ts +++ b/src/services/storage/metricsEventStorage.ts @@ -6,20 +6,26 @@ const chunkSizeBytes = 128 * 1024 export default class { settings: StorageSettings metricsPath: string + cachePath: string metaReady = false metricsMeta: Record> = {} pendingMetrics: Record> = {} - last24hOk: Record = {} - last24hFail: Record = {} - lastPersisted: number = 0 + last24hCache: { ts: number, ok: number, fail: number }[] = [] + lastPersistedMetrics: number = 0 + lastPersistedCache: number = 0 constructor(settings: StorageSettings) { this.settings = settings; this.metricsPath = [settings.dataDir, "metric_events"].join("/") + this.cachePath = [settings.dataDir, "metric_cache"].join("/") this.initMetricsMeta() + this.loadCache() setInterval(() => { - if (Date.now() - this.lastPersisted > 1000 * 60 * 5) { + if (Date.now() - this.lastPersistedMetrics > 1000 * 60 * 4) { this.persistMetrics() } + if (Date.now() - this.lastPersistedCache > 1000 * 60 * 4) { + this.persistCache() + } }, 1000 * 60 * 5) process.on('exit', () => { this.persistMetrics() @@ -39,7 +45,50 @@ export default class { }); } - AddMetricEvent = (appId: string, method: string, metric: Uint8Array) => { + getlast24hCache = () => { return this.last24hCache } + + rotateCache = (nowUnix: number) => { + const yesterday = nowUnix - 60 * 60 * 24 + const latest = this.last24hCache.findIndex(c => c.ts >= yesterday) + if (latest === -1) { + this.last24hCache = [] + return + } else if (latest === 0) { + return + } + this.last24hCache = this.last24hCache.slice(latest) + } + + pushToCache = (ok: boolean) => { + const now = Math.floor(Date.now() / 1000) + this.rotateCache(now) + if (this.last24hCache.length === 0) { + this.last24hCache.push({ ts: now, ok: ok ? 1 : 0, fail: ok ? 0 : 1 }) + return + } + const last = this.last24hCache[this.last24hCache.length - 1] + if (last.ts === now) { + last.ok += ok ? 1 : 0 + last.fail += ok ? 0 : 1 + } else { + this.last24hCache.push({ ts: now, ok: ok ? 1 : 0, fail: ok ? 0 : 1 }) + } + } + + persistCache = () => { + const last24CachePath = [this.cachePath, "last24hSF.json"].join("/") + fs.writeFileSync(last24CachePath, JSON.stringify(this.last24hCache)) + } + + loadCache = () => { + const last24CachePath = [this.cachePath, "last24hSF.json"].join("/") + if (fs.existsSync(last24CachePath)) { + this.last24hCache = JSON.parse(fs.readFileSync(last24CachePath, 'utf-8')) + this.rotateCache(Math.floor(Date.now() / 1000)) + } + } + + AddMetricEvent = (appId: string, method: string, metric: Uint8Array, success: boolean) => { if (!this.metaReady) { throw new Error("meta metrics not ready") } @@ -50,6 +99,8 @@ export default class { this.pendingMetrics[appId][method] = { tlvs: [] } } this.pendingMetrics[appId][method].tlvs.push(metric) + this.pushToCache(success) + } LoadLatestMetrics = async (): Promise => { @@ -78,7 +129,7 @@ export default class { if (!this.metaReady) { throw new Error("meta metrics not ready") } - this.lastPersisted = Date.now() + this.lastPersistedMetrics = Date.now() const tosync = this.pendingMetrics this.pendingMetrics = {} const apps = Object.keys(tosync) From df78bb86f7e883b07a055c7c837653ce32e3d72a Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 17:46:21 +0000 Subject: [PATCH 26/35] fix --- src/services/metrics/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index bcfcbc58..0a586e10 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -82,6 +82,9 @@ export default class Handler { past10m: { errors: 0, total: 0, from_unix: nowUnix - 60 * 10 }, past1m: { errors: 0, total: 0, from_unix: nowUnix - 60 }, } + if (last24h.length === 0) { + return stats + } for (let i = last24h.length; i >= 0; i--) { const e = last24h[i] if (e.ts < stats.past24h.from_unix) { From be19204f6c2037963e2ca3eace9f547b31359777 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 17:49:54 +0000 Subject: [PATCH 27/35] deb --- src/services/storage/metricsEventStorage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/storage/metricsEventStorage.ts b/src/services/storage/metricsEventStorage.ts index fc7017d5..edaa9fd3 100644 --- a/src/services/storage/metricsEventStorage.ts +++ b/src/services/storage/metricsEventStorage.ts @@ -60,6 +60,7 @@ export default class { } pushToCache = (ok: boolean) => { + console.log("pushing to cache", ok) const now = Math.floor(Date.now() / 1000) this.rotateCache(now) if (this.last24hCache.length === 0) { From 95dba81d779f6485e2e1e85bcfbe698ecf004ce4 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 17:54:20 +0000 Subject: [PATCH 28/35] deb --- src/services/metrics/index.ts | 1 + src/services/storage/metricsEventStorage.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 0a586e10..ebe993db 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -87,6 +87,7 @@ export default class Handler { } for (let i = last24h.length; i >= 0; i--) { const e = last24h[i] + console.log(e) if (e.ts < stats.past24h.from_unix) { break } diff --git a/src/services/storage/metricsEventStorage.ts b/src/services/storage/metricsEventStorage.ts index edaa9fd3..fc7017d5 100644 --- a/src/services/storage/metricsEventStorage.ts +++ b/src/services/storage/metricsEventStorage.ts @@ -60,7 +60,6 @@ export default class { } pushToCache = (ok: boolean) => { - console.log("pushing to cache", ok) const now = Math.floor(Date.now() / 1000) this.rotateCache(now) if (this.last24hCache.length === 0) { From d484f58224c340cdf3ae1d443a4a6a8c1499c85d Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 17:56:23 +0000 Subject: [PATCH 29/35] fix --- src/services/metrics/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index ebe993db..6fa06c54 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -82,10 +82,7 @@ export default class Handler { past10m: { errors: 0, total: 0, from_unix: nowUnix - 60 * 10 }, past1m: { errors: 0, total: 0, from_unix: nowUnix - 60 }, } - if (last24h.length === 0) { - return stats - } - for (let i = last24h.length; i >= 0; i--) { + for (let i = last24h.length - 1; i >= 0; i--) { const e = last24h[i] console.log(e) if (e.ts < stats.past24h.from_unix) { From 1a2a715d593cd2b373d0214e3ef0947e6f9020c6 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 17:58:31 +0000 Subject: [PATCH 30/35] persist cache --- src/services/storage/metricsEventStorage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/storage/metricsEventStorage.ts b/src/services/storage/metricsEventStorage.ts index fc7017d5..49a5ac54 100644 --- a/src/services/storage/metricsEventStorage.ts +++ b/src/services/storage/metricsEventStorage.ts @@ -29,6 +29,7 @@ export default class { }, 1000 * 60 * 5) process.on('exit', () => { this.persistMetrics() + this.persistCache() }); // catch ctrl+c event and exit normally From 6c458686a7ca00ce4d1ea4ce2aa53cff7ebaca3c Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 18:06:45 +0000 Subject: [PATCH 31/35] init dir --- src/services/storage/metricsEventStorage.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/storage/metricsEventStorage.ts b/src/services/storage/metricsEventStorage.ts index 49a5ac54..11727613 100644 --- a/src/services/storage/metricsEventStorage.ts +++ b/src/services/storage/metricsEventStorage.ts @@ -17,6 +17,9 @@ export default class { this.settings = settings; this.metricsPath = [settings.dataDir, "metric_events"].join("/") this.cachePath = [settings.dataDir, "metric_cache"].join("/") + if (!fs.existsSync(this.cachePath)) { + fs.mkdirSync(this.cachePath, { recursive: true }); + } this.initMetricsMeta() this.loadCache() setInterval(() => { @@ -78,7 +81,7 @@ export default class { persistCache = () => { const last24CachePath = [this.cachePath, "last24hSF.json"].join("/") - fs.writeFileSync(last24CachePath, JSON.stringify(this.last24hCache)) + fs.writeFileSync(last24CachePath, JSON.stringify(this.last24hCache), {}) } loadCache = () => { From f364c9ed52c6e775b191b695f1300e693be18060 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 18:09:34 +0000 Subject: [PATCH 32/35] need err --- src/services/metrics/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 6fa06c54..4413b242 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -82,7 +82,7 @@ export default class Handler { past10m: { errors: 0, total: 0, from_unix: nowUnix - 60 * 10 }, past1m: { errors: 0, total: 0, from_unix: nowUnix - 60 }, } - for (let i = last24h.length - 1; i >= 0; i--) { + for (let i = last24h.length; i >= 0; i--) { const e = last24h[i] console.log(e) if (e.ts < stats.past24h.from_unix) { From 02479c9967001f4be55b5f4dd84956f06c0e03d2 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 9 Jan 2025 18:11:05 +0000 Subject: [PATCH 33/35] fix err --- src/services/metrics/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 4413b242..6fa06c54 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -82,7 +82,7 @@ export default class Handler { past10m: { errors: 0, total: 0, from_unix: nowUnix - 60 * 10 }, past1m: { errors: 0, total: 0, from_unix: nowUnix - 60 }, } - for (let i = last24h.length; i >= 0; i--) { + for (let i = last24h.length - 1; i >= 0; i--) { const e = last24h[i] console.log(e) if (e.ts < stats.past24h.from_unix) { From 1b63d27e40481b094fdf99548992e886300f43dd Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 10 Jan 2025 14:17:44 +0000 Subject: [PATCH 34/35] less log --- src/services/metrics/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 6fa06c54..be5690bf 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -84,7 +84,6 @@ export default class Handler { } for (let i = last24h.length - 1; i >= 0; i--) { const e = last24h[i] - console.log(e) if (e.ts < stats.past24h.from_unix) { break } From 88ffb41f05da43418066c86595676e65bc7be2d9 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Tue, 14 Jan 2025 15:51:29 +0000 Subject: [PATCH 35/35] fixes --- .gitignore | 2 ++ scripts/extract_nprofile.sh | 12 ++++++------ scripts/install_lightning_pub.sh | 2 +- src/services/metrics/index.ts | 3 ++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index da385481..0e58d9f6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,8 +15,10 @@ data/ .wallet_secret .wallet_password .admin_enroll +admin.enroll admin.npub app.nprofile .admin_connect +admin.connect debug.txt proto/autogenerated/debug.txt diff --git a/scripts/extract_nprofile.sh b/scripts/extract_nprofile.sh index 422105fb..81c01ba5 100644 --- a/scripts/extract_nprofile.sh +++ b/scripts/extract_nprofile.sh @@ -64,11 +64,11 @@ get_log_info() { log "Retrieving connection information..." - # Wait for either .admin_connect or app.nprofile to appear + # Wait for either admin.connect or app.nprofile to appear START_TIME=$(date +%s) while [ $(($(date +%s) - START_TIME)) -lt $MAX_WAIT_TIME ]; do - if [ -f "$DATA_DIR/.admin_connect" ]; then - admin_connect=$(cat "$DATA_DIR/.admin_connect") + if [ -f "$DATA_DIR/admin.connect" ]; then + admin_connect=$(cat "$DATA_DIR/admin.connect") # Check if the admin_connect string is complete (contains both nprofile and secret) if [[ $admin_connect == nprofile* ]] && [[ $admin_connect == *:* ]]; then log "An admin has not yet been enrolled." @@ -87,10 +87,10 @@ get_log_info() { sleep $WAIT_INTERVAL done - if [ ! -f "$DATA_DIR/.admin_connect" ] && [ ! -f "$DATA_DIR/app.nprofile" ]; then - log "Neither .admin_connect nor app.nprofile file found after waiting. Please check the service status." + if [ ! -f "$DATA_DIR/admin.connect" ] && [ ! -f "$DATA_DIR/app.nprofile" ]; then + log "Neither admin.connect nor app.nprofile file found after waiting. Please check the service status." exit 1 - elif [ -f "$DATA_DIR/.admin_connect" ] && ! [[ $(cat "$DATA_DIR/.admin_connect") == nprofile1* ]] && ! [[ $(cat "$DATA_DIR/.admin_connect") == *:* ]]; then + elif [ -f "$DATA_DIR/admin.connect" ] && ! [[ $(cat "$DATA_DIR/admin.connect") == nprofile1* ]] && ! [[ $(cat "$DATA_DIR/admin.connect") == *:* ]]; then log "Admin connect information is incomplete. Please check the service status." exit 1 fi diff --git a/scripts/install_lightning_pub.sh b/scripts/install_lightning_pub.sh index fd10ce04..132a29d2 100755 --- a/scripts/install_lightning_pub.sh +++ b/scripts/install_lightning_pub.sh @@ -41,7 +41,7 @@ install_lightning_pub() { # Merge if upgrade if [ $upgrade_status -eq 100 ]; then - rsync -a --quiet --exclude='*.sqlite' --exclude='.env' --exclude='logs' --exclude='node_modules' --exclude='.jwt_secret' --exclude='.wallet_secret' --exclude='admin.npub' --exclude='app.nprofile' --exclude='.admin_connect' --exclude='.admin_enroll' $USER_HOME/lightning_pub_temp/ $USER_HOME/lightning_pub/ + rsync -a --quiet --exclude='*.sqlite' --exclude='.env' --exclude='logs' --exclude='node_modules' --exclude='.jwt_secret' --exclude='.wallet_secret' --exclude='admin.npub' --exclude='app.nprofile' --exclude='admin.connect' --exclude='admin.enroll' $USER_HOME/lightning_pub_temp/ $USER_HOME/lightning_pub/ else mv $USER_HOME/lightning_pub_temp $USER_HOME/lightning_pub fi diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index be5690bf..307992f3 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -11,6 +11,7 @@ import HtlcTracker from './htlcTracker.js' import { MainSettings } from '../main/settings.js' import { getLogger } from '../helpers/logger.js' import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.js' +import { ChannelCloseSummary_ClosureType } from '../../../proto/lnd/lightning.js' export default class Handler { @@ -321,7 +322,7 @@ export default class Handler { externalBalance.push({ x: e.block_height, y: e.external_balance }) } }) - const closed = await Promise.all(closedChannels.map(async c => { + const closed = await Promise.all(closedChannels.filter(c => c.closeType !== ChannelCloseSummary_ClosureType.FUNDING_CANCELED).map(async c => { const tx = await this.lnd.GetTx(c.closingTxHash) return { capacity: Number(c.capacity), channel_id: c.chanId, closed_height: c.closeHeight, close_tx_timestamp: Number(tx.timeStamp) } }))