From d3470c47fef8d1654069d3c89771f3a3f7d67012 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Wed, 12 Oct 2022 16:41:22 +0200 Subject: [PATCH 01/33] feat(open-payments-client): initial POC of open-payments-client --- packages/open-payments-client/package.json | 23 + packages/open-payments-client/src/index.ts | 76 +++ packages/open-payments-client/src/types.ts | 708 ++++++++++++++++++++ packages/open-payments-client/tsconfig.json | 11 + 4 files changed, 818 insertions(+) create mode 100644 packages/open-payments-client/package.json create mode 100644 packages/open-payments-client/src/index.ts create mode 100644 packages/open-payments-client/src/types.ts create mode 100644 packages/open-payments-client/tsconfig.json diff --git a/packages/open-payments-client/package.json b/packages/open-payments-client/package.json new file mode 100644 index 0000000000..f99a55f866 --- /dev/null +++ b/packages/open-payments-client/package.json @@ -0,0 +1,23 @@ +{ + "name": "open-payments-client", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "scripts": { + "build": "pnpm clean && tsc --build tsconfig.json", + "clean": "rm -fr dist/", + "prepack": "pnpm build", + "generate": "npx openapi-typescript https://raw.githubusercontent.com/interledger/open-payments/main/open-api-spec.yaml --output src/types.ts" + }, + "devDependencies": { + "openapi-typescript-codegen": "^0.23.0", + "typescript": "^4.2.4" + }, + "dependencies": { + "axios": "^1.1.2" + } +} diff --git a/packages/open-payments-client/src/index.ts b/packages/open-payments-client/src/index.ts new file mode 100644 index 0000000000..0ef80025b0 --- /dev/null +++ b/packages/open-payments-client/src/index.ts @@ -0,0 +1,76 @@ +import { components } from './types' +import axios, { AxiosInstance } from 'axios' + +type IncomingPayment = components['schemas']['incoming-payment'] + +interface CreateOpenPaymentClientArgs { + timeout?: number +} + +interface GetArgs { + url: string + accessToken: string +} + +interface OpenPaymentsClient { + incomingPayment: { + get(args: GetArgs): Promise + } +} + +const get = async (axios: AxiosInstance, args: GetArgs): Promise => { + const { url, accessToken } = args + + const headers = { + 'Content-Type': 'application/json' + } + + if (accessToken) { + headers['Authorization'] = `GNAP ${accessToken}` + // TODO: https://github.com/interledger/rafiki/issues/587 + headers['Signature'] = 'TODO' + headers['Signature-Input'] = 'TODO' + } + + const { data } = await axios.get(url, { + headers + }) + + return data +} + +const getIncomingPayment = async ( + axios: AxiosInstance, + args: GetArgs +): Promise => { + return get(axios, args) +} + +const createClient = ( + args: CreateOpenPaymentClientArgs +): OpenPaymentsClient => { + const { timeout } = args + + const axiosInstance = axios.create({ + timeout + }) + + return { + incomingPayment: { + get: (getArgs: GetArgs) => getIncomingPayment(axiosInstance, getArgs) + } + } +} + +const openPaymentsClient = createClient({ + timeout: 5_000 +}) + +const incomingPayment = await openPaymentsClient.incomingPayment.get({ + url: '', + accessToken: '' +}) + + +incomingPayment. +export { IncomingPayment, OpenPaymentsClient, createClient } diff --git a/packages/open-payments-client/src/types.ts b/packages/open-payments-client/src/types.ts new file mode 100644 index 0000000000..30fa869425 --- /dev/null +++ b/packages/open-payments-client/src/types.ts @@ -0,0 +1,708 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/": { + /** + * Retrieve the public information of the Payment Pointer. + * + * This end-point should be open to anonymous requests as it allows clients to verify a Payment Pointer URL and get the basic information required to construct new transactions and discover the grant request URL. + * + * The content should be slow changing and cacheable for long periods. Servers SHOULD use cache control headers. + */ + get: operations["get-payment-pointer"]; + }; + "/connections/{id}": { + /** + * *NB* Use server url specific to this path. + * + * Fetch new connection credentials for an ILP STREAM connection. + * + * A connection is an ephemeral resource that is created to accommodate new incoming payments. + * + * A new set of credential will be generated each time this API is called. + */ + get: operations["get-ilp-stream-connection"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; + "/incoming-payments": { + /** List all incoming payments on the payment pointer */ + get: operations["list-incoming-payments"]; + /** + * A client MUST create an **incoming payment** resource before it is possible to send any payments to the payment pointer. + * + * All of the input parameters are _optional_. + */ + post: operations["create-incoming-payment"]; + }; + /** Create a new outgoing payment at the payment pointer. */ + "/outgoing-payments": { + /** List all outgoing payments on the payment pointer */ + get: operations["list-outgoing-payments"]; + /** An **outgoing payment** is a sub-resource of a payment pointer. It represents a payment from the payment pointer. */ + post: operations["create-outgoing-payment"]; + }; + /** Create a new quote at the payment pointer. */ + "/quotes": { + /** A **quote** is a sub-resource of a payment pointer. It represents a quote for a payment from the payment pointer. */ + post: operations["create-quote"]; + }; + "/incoming-payments/{id}": { + /** A client can fetch the latest state of an incoming payment to determine the amount received into the payment pointer. */ + get: operations["get-incoming-payment"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; + "/incoming-payments/{id}/complete": { + /** + * A client with the appropriate permissions MAY mark a non-expired **incoming payment** as `completed` if it has not yet received `incomingAmount`. + * + * This indicates to the receiving account provider that it can begin any post processing of the payment such as generating account statements or notifying the account holder of the completed payment. + */ + post: operations["complete-incoming-payment"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; + "/outgoing-payments/{id}": { + /** A client can fetch the latest state of an outgoing payment. */ + get: operations["get-outgoing-payment"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; + "/quotes/{id}": { + /** A client can fetch the latest state of a quote. */ + get: operations["get-quote"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; +} + +export interface components { + schemas: { + /** + * Payment Pointer + * @description A **payment pointer** resource is the root of the API and contains the public details of the financial account represented by the Payment Pointer that is also the service endpoint URL. + */ + "payment-pointer": { + /** + * Format: uri + * @description The URL identifying the incoming payment. + */ + id: string; + /** @description A public name for the account. This should be set by the account holder with their provider to provide a hint to counterparties as to the identity of the account holder. */ + publicName?: string; + /** @description The asset code of the account. */ + assetCode: components["schemas"]["assetCode"]; + assetScale: components["schemas"]["assetScale"]; + /** + * Format: uri + * @description The URL of the authorization server endpoint for getting grants and access tokens for this payment pointer. + */ + authServer: string; + }; + /** + * ILP Stream Connection + * @description An **ILP STREAM Connection** is an endpoint that returns unique STREAM connection credentials to establish a STREAM connection to the underlying account. + */ + "ilp-stream-connection": { + /** + * Format: uri + * @description The URL identifying the endpoint. + */ + id: string; + /** @description The ILP address to use when establishing a STREAM connection. */ + ilpAddress: string; + /** @description The base64 url-encoded shared secret to use when establishing a STREAM connection. */ + sharedSecret: string; + /** @description The asset code of the amount. */ + assetCode: components["schemas"]["assetCode"]; + /** @description The scale of the amount. */ + assetScale: components["schemas"]["assetScale"]; + }; + /** + * Incoming Payment + * @description An **incoming payment** resource represents a payment that will be, is currently being, or has been received by the account. + */ + "incoming-payment": { + /** + * Format: uri + * @description The URL identifying the incoming payment. + */ + id: string; + /** + * Format: uri + * @description The URL of the payment pointer this payment is being made into. + */ + paymentPointer: string; + /** + * @description Describes whether the incoming payment has completed receiving fund. + * @default false + */ + completed: boolean; + /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ + incomingAmount?: components["schemas"]["amount"]; + /** @description The total amount that has been paid into the payment pointer under this incoming payment. */ + receivedAmount: components["schemas"]["amount"]; + /** + * Format: date-time + * @description The date and time when payments under this incoming payment will no longer be accepted. + */ + expiresAt?: string; + /** @description Human readable description of the incoming payment that will be visible to the account holder. */ + description?: string; + /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. */ + externalRef?: string; + /** + * Format: date-time + * @description The date and time when the incoming payment was created. + */ + createdAt: string; + /** + * Format: date-time + * @description The date and time when the incoming payment was updated. + */ + updatedAt: string; + }; + /** + * Incoming Payment with Connection + * @description An **incoming payment** resource with the Interledger STREAM Connection to use to pay into the payment pointer under this incoming payment. + */ + "incoming-payment-with-connection": components["schemas"]["incoming-payment"] & { + ilpStreamConnection?: components["schemas"]["ilp-stream-connection"]; + }; + /** + * Incoming Payment with Connection + * @description An **incoming payment** resource with the url for the Interledger STREAM Connection resource to use to pay into the payment pointer under this incoming payment. + */ + "incoming-payment-with-connection-url": components["schemas"]["incoming-payment"] & { + /** + * Format: uri + * @description Endpoint that returns unique STREAM connection credentials to establish a STREAM connection to the underlying account. + */ + ilpStreamConnection?: string; + }; + /** + * Outgoing Payment + * @description An **outgoing payment** resource represents a payment that will be, is currently being, or has previously been, sent from the payment pointer. + */ + "outgoing-payment": { + /** + * Format: uri + * @description The URL identifying the outgoing payment. + */ + id: string; + /** + * Format: uri + * @description The URL of the payment pointer from which this payment is sent. + */ + paymentPointer: string; + /** + * Format: uri + * @description The URL of the quote defining this payment's amounts. + */ + quoteId?: string; + /** + * @description Describes whether the payment failed to send its full amount. + * @default false + */ + failed?: boolean; + /** @description The URL of the incoming payment or ILP STREAM Connection that is being paid. */ + receiver: components["schemas"]["receiver"]; + /** @description The total amount that should be received by the receiver when this outgoing payment has been paid. */ + receiveAmount: components["schemas"]["amount"]; + /** @description The total amount that should be sent when this outgoing payment has been paid. */ + sendAmount: components["schemas"]["amount"]; + /** @description The total amount that has been sent under this outgoing payment. */ + sentAmount: components["schemas"]["amount"]; + /** @description Human readable description of the outgoing payment that will be visible to the account holder and shared with the receiver. */ + description?: string; + /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ + externalRef?: string; + /** + * Format: date-time + * @description The date and time when the outgoing payment was created. + */ + createdAt: string; + /** + * Format: date-time + * @description The date and time when the outgoing payment was updated. + */ + updatedAt: string; + }; + /** + * Quote + * @description A **quote** resource represents the quoted amount details with which an Outgoing Payment may be created. + */ + quote: { + /** + * Format: uri + * @description The URL identifying the quote. + */ + id: string; + /** + * Format: uri + * @description The URL of the payment pointer from which this quote's payment would be sent. + */ + paymentPointer: string; + /** @description The URL of the incoming payment or ILP Stream Connection that would be paid. */ + receiver: components["schemas"]["receiver"]; + /** @description The total amount that should be received by the receiver. */ + receiveAmount: components["schemas"]["amount"]; + /** @description The total amount that should be sent by the sender. */ + sendAmount: components["schemas"]["amount"]; + /** @description The date and time when the calculated `sendAmount` is no longer valid. */ + expiresAt?: string; + /** + * Format: date-time + * @description The date and time when the quote was created. + */ + createdAt: string; + }; + /** + * Amount + * @description All amounts in open payments are represented as a value and an asset code and scale. + * + * The `value` is an unsigned 64-bit integer amount, represented as a string. + * + * The `assetCode` is a code that indicates the underlying asset. In most cases this SHOULD be a 3-character ISO 4217 currency code. + * + * The `assetScale` indicates how the `value` has been scaled relative to the natural scale of the asset. For example, an `value` of `"1234"` with an `assetScale` of `2` represents an amount of 12.34. + */ + amount: { + /** + * Format: uint64 + * @description The amount, scaled by the given scale. + */ + value: string; + /** @description The asset code of the amount. */ + assetCode: components["schemas"]["assetCode"]; + /** @description The scale of the amount. */ + assetScale: components["schemas"]["assetScale"]; + }; + /** + * Asset code + * @description This SHOULD be an ISO4217 currency code. + */ + assetCode: string; + /** + * Asset scale + * @description The scale of amounts denoted in the corresponding asset code. + */ + assetScale: number; + /** + * Receiver + * Format: uri + * @description The URL of the incoming payment or ILP STREAM connection that is being paid. + */ + receiver: string; + /** @description Pagination parameters */ + pagination: + | components["schemas"]["forward-pagination"] + | components["schemas"]["backward-pagination"]; + /** @description Forward pagination parameters */ + "forward-pagination": { + /** @description The number of items to return. */ + first?: number; + /** @description The cursor key to list from. */ + cursor?: string; + }; + /** @description Backward pagination parameters */ + "backward-pagination": { + /** @description The number of items to return. */ + last?: number; + /** @description The cursor key to list from. */ + cursor: string; + }; + "page-info": { + /** @description Cursor corresponding to the first element in the result array. */ + startCursor: string; + /** @description Cursor corresponding to the last element in the result array. */ + endCursor: string; + /** @description Describes whether the data set has further entries. */ + hasNextPage: boolean; + /** @description Describes whether the data set has previous entries. */ + hasPreviousPage: boolean; + }; + }; + responses: { + /** Authorization required */ + 401: unknown; + /** Forbidden */ + 403: unknown; + }; + parameters: { + /** @description Sub-resource identifier */ + id: string; + /** @description The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + signature: string; + /** @description The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "signature-input": string; + }; +} + +export interface operations { + /** + * Retrieve the public information of the Payment Pointer. + * + * This end-point should be open to anonymous requests as it allows clients to verify a Payment Pointer URL and get the basic information required to construct new transactions and discover the grant request URL. + * + * The content should be slow changing and cacheable for long periods. Servers SHOULD use cache control headers. + */ + "get-payment-pointer": { + responses: { + /** Payment Pointer Found */ + 200: { + content: { + "application/json": components["schemas"]["payment-pointer"]; + }; + }; + /** Payment Pointer Not Found */ + 404: unknown; + }; + }; + /** + * *NB* Use server url specific to this path. + * + * Fetch new connection credentials for an ILP STREAM connection. + * + * A connection is an ephemeral resource that is created to accommodate new incoming payments. + * + * A new set of credential will be generated each time this API is called. + */ + "get-ilp-stream-connection": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + responses: { + /** Connection Found */ + 200: { + content: { + "application/json": components["schemas"]["ilp-stream-connection"]; + }; + }; + /** Connection Not Found */ + 404: unknown; + }; + }; + /** List all incoming payments on the payment pointer */ + "list-incoming-payments": { + parameters: { + query: { + /** Pagination parameters */ + pagination?: components["schemas"]["pagination"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": { + pagination?: components["schemas"]["page-info"]; + result?: components["schemas"]["incoming-payment-with-connection-url"][]; + }; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + }; + /** + * A client MUST create an **incoming payment** resource before it is possible to send any payments to the payment pointer. + * + * All of the input parameters are _optional_. + */ + "create-incoming-payment": { + parameters: { + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Incoming Payment Created */ + 201: { + content: { + "application/json": components["schemas"]["incoming-payment-with-connection"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + /** + * A subset of the incoming payments schema is accepted as input to create a new incoming payment. + * + * The `incomingAmount` must use the same `assetCode` and `assetScale` as the payment pointer. + */ + requestBody: { + content: { + "application/json": { + /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ + incomingAmount?: components["schemas"]["amount"]; + /** + * Format: date-time + * @description The date and time when payments into the incoming payment must no longer be accepted. + */ + expiresAt?: string; + /** @description Human readable description of the incoming payment that will be visible to the account holder. */ + description?: string; + /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ + externalRef?: string; + }; + }; + }; + }; + /** List all outgoing payments on the payment pointer */ + "list-outgoing-payments": { + parameters: { + query: { + /** Pagination parameters */ + pagination?: components["schemas"]["pagination"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": { + pagination?: components["schemas"]["page-info"]; + result?: components["schemas"]["outgoing-payment"][]; + }; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + }; + /** An **outgoing payment** is a sub-resource of a payment pointer. It represents a payment from the payment pointer. */ + "create-outgoing-payment": { + parameters: { + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Outgoing Payment Created */ + 201: { + content: { + "application/json": components["schemas"]["outgoing-payment"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + /** + * A subset of the outgoing payments schema is accepted as input to create a new outgoing payment. + * + * The `sendAmount` must use the same `assetCode` and `assetScale` as the payment pointer. + */ + requestBody: { + content: { + "application/json": { + /** + * Format: uri + * @description The URL of the quote defining this payment's amounts. + */ + quoteId: string; + /** @description Human readable description of the outgoing payment that will be visible to the account holder and shared with the receiver. */ + description?: string; + /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ + externalRef?: string; + }; + }; + }; + }; + /** A **quote** is a sub-resource of a payment pointer. It represents a quote for a payment from the payment pointer. */ + "create-quote": { + parameters: { + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Quote Created */ + 201: { + content: { + "application/json": components["schemas"]["quote"]; + }; + }; + /** No amount was provided and no amount could be inferred from the receiver. */ + 400: unknown; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + /** + * A subset of the quotes schema is accepted as input to create a new quote. + * + * The quote must be created with a (`sendAmount` xor `receiveAmount`) unless the `receiver` is an Incoming Payment which has an `incomingAmount`. + */ + requestBody: { + content: { + "application/json": { + receiver: components["schemas"]["receiver"]; + receiveAmount?: components["schemas"]["amount"]; + sendAmount?: components["schemas"]["amount"]; + }; + }; + }; + }; + /** A client can fetch the latest state of an incoming payment to determine the amount received into the payment pointer. */ + "get-incoming-payment": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Incoming Payment Found */ + 200: { + content: { + "application/json": components["schemas"]["incoming-payment-with-connection"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + /** Incoming Payment Not Found */ + 404: unknown; + }; + }; + /** + * A client with the appropriate permissions MAY mark a non-expired **incoming payment** as `completed` if it has not yet received `incomingAmount`. + * + * This indicates to the receiving account provider that it can begin any post processing of the payment such as generating account statements or notifying the account holder of the completed payment. + */ + "complete-incoming-payment": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["incoming-payment"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + /** Incoming Payment Not Found */ + 404: unknown; + }; + }; + /** A client can fetch the latest state of an outgoing payment. */ + "get-outgoing-payment": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Outgoing Payment Found */ + 200: { + content: { + "application/json": components["schemas"]["outgoing-payment"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + /** Outgoing Payment Not Found */ + 404: unknown; + }; + }; + /** A client can fetch the latest state of a quote. */ + "get-quote": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Quote Found */ + 200: { + content: { + "application/json": components["schemas"]["quote"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + /** Quote Not Found */ + 404: unknown; + }; + }; +} + +export interface external {} diff --git a/packages/open-payments-client/tsconfig.json b/packages/open-payments-client/tsconfig.json new file mode 100644 index 0000000000..c7e406b567 --- /dev/null +++ b/packages/open-payments-client/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "declaration": true + }, + "include": ["src/**/*"], + "exclude": ["**/*.test.ts"] +} From 7f968c3c71bfb24a97636ec6d33a53bf0e8d58b4 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Wed, 12 Oct 2022 16:48:49 +0200 Subject: [PATCH 02/33] feat(open-payments-client): cleanup --- packages/open-payments-client/src/index.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/open-payments-client/src/index.ts b/packages/open-payments-client/src/index.ts index 0ef80025b0..cb95ab0299 100644 --- a/packages/open-payments-client/src/index.ts +++ b/packages/open-payments-client/src/index.ts @@ -27,7 +27,6 @@ const get = async (axios: AxiosInstance, args: GetArgs): Promise => { if (accessToken) { headers['Authorization'] = `GNAP ${accessToken}` - // TODO: https://github.com/interledger/rafiki/issues/587 headers['Signature'] = 'TODO' headers['Signature-Input'] = 'TODO' } @@ -62,15 +61,4 @@ const createClient = ( } } -const openPaymentsClient = createClient({ - timeout: 5_000 -}) - -const incomingPayment = await openPaymentsClient.incomingPayment.get({ - url: '', - accessToken: '' -}) - - -incomingPayment. export { IncomingPayment, OpenPaymentsClient, createClient } From 79e7cd7cf8dc30f1988c30fe9c317cc9f987649a Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Thu, 13 Oct 2022 17:54:16 +0200 Subject: [PATCH 03/33] feat(open-payments): remove open-payments-client folder --- packages/open-payments-client/package.json | 23 - packages/open-payments-client/src/index.ts | 64 -- packages/open-payments-client/src/types.ts | 708 -------------------- packages/open-payments-client/tsconfig.json | 11 - 4 files changed, 806 deletions(-) delete mode 100644 packages/open-payments-client/package.json delete mode 100644 packages/open-payments-client/src/index.ts delete mode 100644 packages/open-payments-client/src/types.ts delete mode 100644 packages/open-payments-client/tsconfig.json diff --git a/packages/open-payments-client/package.json b/packages/open-payments-client/package.json deleted file mode 100644 index f99a55f866..0000000000 --- a/packages/open-payments-client/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "open-payments-client", - "version": "1.0.0", - "description": "", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist/**/*" - ], - "scripts": { - "build": "pnpm clean && tsc --build tsconfig.json", - "clean": "rm -fr dist/", - "prepack": "pnpm build", - "generate": "npx openapi-typescript https://raw.githubusercontent.com/interledger/open-payments/main/open-api-spec.yaml --output src/types.ts" - }, - "devDependencies": { - "openapi-typescript-codegen": "^0.23.0", - "typescript": "^4.2.4" - }, - "dependencies": { - "axios": "^1.1.2" - } -} diff --git a/packages/open-payments-client/src/index.ts b/packages/open-payments-client/src/index.ts deleted file mode 100644 index cb95ab0299..0000000000 --- a/packages/open-payments-client/src/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { components } from './types' -import axios, { AxiosInstance } from 'axios' - -type IncomingPayment = components['schemas']['incoming-payment'] - -interface CreateOpenPaymentClientArgs { - timeout?: number -} - -interface GetArgs { - url: string - accessToken: string -} - -interface OpenPaymentsClient { - incomingPayment: { - get(args: GetArgs): Promise - } -} - -const get = async (axios: AxiosInstance, args: GetArgs): Promise => { - const { url, accessToken } = args - - const headers = { - 'Content-Type': 'application/json' - } - - if (accessToken) { - headers['Authorization'] = `GNAP ${accessToken}` - headers['Signature'] = 'TODO' - headers['Signature-Input'] = 'TODO' - } - - const { data } = await axios.get(url, { - headers - }) - - return data -} - -const getIncomingPayment = async ( - axios: AxiosInstance, - args: GetArgs -): Promise => { - return get(axios, args) -} - -const createClient = ( - args: CreateOpenPaymentClientArgs -): OpenPaymentsClient => { - const { timeout } = args - - const axiosInstance = axios.create({ - timeout - }) - - return { - incomingPayment: { - get: (getArgs: GetArgs) => getIncomingPayment(axiosInstance, getArgs) - } - } -} - -export { IncomingPayment, OpenPaymentsClient, createClient } diff --git a/packages/open-payments-client/src/types.ts b/packages/open-payments-client/src/types.ts deleted file mode 100644 index 30fa869425..0000000000 --- a/packages/open-payments-client/src/types.ts +++ /dev/null @@ -1,708 +0,0 @@ -/** - * This file was auto-generated by openapi-typescript. - * Do not make direct changes to the file. - */ - -export interface paths { - "/": { - /** - * Retrieve the public information of the Payment Pointer. - * - * This end-point should be open to anonymous requests as it allows clients to verify a Payment Pointer URL and get the basic information required to construct new transactions and discover the grant request URL. - * - * The content should be slow changing and cacheable for long periods. Servers SHOULD use cache control headers. - */ - get: operations["get-payment-pointer"]; - }; - "/connections/{id}": { - /** - * *NB* Use server url specific to this path. - * - * Fetch new connection credentials for an ILP STREAM connection. - * - * A connection is an ephemeral resource that is created to accommodate new incoming payments. - * - * A new set of credential will be generated each time this API is called. - */ - get: operations["get-ilp-stream-connection"]; - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - }; - }; - "/incoming-payments": { - /** List all incoming payments on the payment pointer */ - get: operations["list-incoming-payments"]; - /** - * A client MUST create an **incoming payment** resource before it is possible to send any payments to the payment pointer. - * - * All of the input parameters are _optional_. - */ - post: operations["create-incoming-payment"]; - }; - /** Create a new outgoing payment at the payment pointer. */ - "/outgoing-payments": { - /** List all outgoing payments on the payment pointer */ - get: operations["list-outgoing-payments"]; - /** An **outgoing payment** is a sub-resource of a payment pointer. It represents a payment from the payment pointer. */ - post: operations["create-outgoing-payment"]; - }; - /** Create a new quote at the payment pointer. */ - "/quotes": { - /** A **quote** is a sub-resource of a payment pointer. It represents a quote for a payment from the payment pointer. */ - post: operations["create-quote"]; - }; - "/incoming-payments/{id}": { - /** A client can fetch the latest state of an incoming payment to determine the amount received into the payment pointer. */ - get: operations["get-incoming-payment"]; - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - }; - }; - "/incoming-payments/{id}/complete": { - /** - * A client with the appropriate permissions MAY mark a non-expired **incoming payment** as `completed` if it has not yet received `incomingAmount`. - * - * This indicates to the receiving account provider that it can begin any post processing of the payment such as generating account statements or notifying the account holder of the completed payment. - */ - post: operations["complete-incoming-payment"]; - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - }; - }; - "/outgoing-payments/{id}": { - /** A client can fetch the latest state of an outgoing payment. */ - get: operations["get-outgoing-payment"]; - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - }; - }; - "/quotes/{id}": { - /** A client can fetch the latest state of a quote. */ - get: operations["get-quote"]; - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - }; - }; -} - -export interface components { - schemas: { - /** - * Payment Pointer - * @description A **payment pointer** resource is the root of the API and contains the public details of the financial account represented by the Payment Pointer that is also the service endpoint URL. - */ - "payment-pointer": { - /** - * Format: uri - * @description The URL identifying the incoming payment. - */ - id: string; - /** @description A public name for the account. This should be set by the account holder with their provider to provide a hint to counterparties as to the identity of the account holder. */ - publicName?: string; - /** @description The asset code of the account. */ - assetCode: components["schemas"]["assetCode"]; - assetScale: components["schemas"]["assetScale"]; - /** - * Format: uri - * @description The URL of the authorization server endpoint for getting grants and access tokens for this payment pointer. - */ - authServer: string; - }; - /** - * ILP Stream Connection - * @description An **ILP STREAM Connection** is an endpoint that returns unique STREAM connection credentials to establish a STREAM connection to the underlying account. - */ - "ilp-stream-connection": { - /** - * Format: uri - * @description The URL identifying the endpoint. - */ - id: string; - /** @description The ILP address to use when establishing a STREAM connection. */ - ilpAddress: string; - /** @description The base64 url-encoded shared secret to use when establishing a STREAM connection. */ - sharedSecret: string; - /** @description The asset code of the amount. */ - assetCode: components["schemas"]["assetCode"]; - /** @description The scale of the amount. */ - assetScale: components["schemas"]["assetScale"]; - }; - /** - * Incoming Payment - * @description An **incoming payment** resource represents a payment that will be, is currently being, or has been received by the account. - */ - "incoming-payment": { - /** - * Format: uri - * @description The URL identifying the incoming payment. - */ - id: string; - /** - * Format: uri - * @description The URL of the payment pointer this payment is being made into. - */ - paymentPointer: string; - /** - * @description Describes whether the incoming payment has completed receiving fund. - * @default false - */ - completed: boolean; - /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ - incomingAmount?: components["schemas"]["amount"]; - /** @description The total amount that has been paid into the payment pointer under this incoming payment. */ - receivedAmount: components["schemas"]["amount"]; - /** - * Format: date-time - * @description The date and time when payments under this incoming payment will no longer be accepted. - */ - expiresAt?: string; - /** @description Human readable description of the incoming payment that will be visible to the account holder. */ - description?: string; - /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. */ - externalRef?: string; - /** - * Format: date-time - * @description The date and time when the incoming payment was created. - */ - createdAt: string; - /** - * Format: date-time - * @description The date and time when the incoming payment was updated. - */ - updatedAt: string; - }; - /** - * Incoming Payment with Connection - * @description An **incoming payment** resource with the Interledger STREAM Connection to use to pay into the payment pointer under this incoming payment. - */ - "incoming-payment-with-connection": components["schemas"]["incoming-payment"] & { - ilpStreamConnection?: components["schemas"]["ilp-stream-connection"]; - }; - /** - * Incoming Payment with Connection - * @description An **incoming payment** resource with the url for the Interledger STREAM Connection resource to use to pay into the payment pointer under this incoming payment. - */ - "incoming-payment-with-connection-url": components["schemas"]["incoming-payment"] & { - /** - * Format: uri - * @description Endpoint that returns unique STREAM connection credentials to establish a STREAM connection to the underlying account. - */ - ilpStreamConnection?: string; - }; - /** - * Outgoing Payment - * @description An **outgoing payment** resource represents a payment that will be, is currently being, or has previously been, sent from the payment pointer. - */ - "outgoing-payment": { - /** - * Format: uri - * @description The URL identifying the outgoing payment. - */ - id: string; - /** - * Format: uri - * @description The URL of the payment pointer from which this payment is sent. - */ - paymentPointer: string; - /** - * Format: uri - * @description The URL of the quote defining this payment's amounts. - */ - quoteId?: string; - /** - * @description Describes whether the payment failed to send its full amount. - * @default false - */ - failed?: boolean; - /** @description The URL of the incoming payment or ILP STREAM Connection that is being paid. */ - receiver: components["schemas"]["receiver"]; - /** @description The total amount that should be received by the receiver when this outgoing payment has been paid. */ - receiveAmount: components["schemas"]["amount"]; - /** @description The total amount that should be sent when this outgoing payment has been paid. */ - sendAmount: components["schemas"]["amount"]; - /** @description The total amount that has been sent under this outgoing payment. */ - sentAmount: components["schemas"]["amount"]; - /** @description Human readable description of the outgoing payment that will be visible to the account holder and shared with the receiver. */ - description?: string; - /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ - externalRef?: string; - /** - * Format: date-time - * @description The date and time when the outgoing payment was created. - */ - createdAt: string; - /** - * Format: date-time - * @description The date and time when the outgoing payment was updated. - */ - updatedAt: string; - }; - /** - * Quote - * @description A **quote** resource represents the quoted amount details with which an Outgoing Payment may be created. - */ - quote: { - /** - * Format: uri - * @description The URL identifying the quote. - */ - id: string; - /** - * Format: uri - * @description The URL of the payment pointer from which this quote's payment would be sent. - */ - paymentPointer: string; - /** @description The URL of the incoming payment or ILP Stream Connection that would be paid. */ - receiver: components["schemas"]["receiver"]; - /** @description The total amount that should be received by the receiver. */ - receiveAmount: components["schemas"]["amount"]; - /** @description The total amount that should be sent by the sender. */ - sendAmount: components["schemas"]["amount"]; - /** @description The date and time when the calculated `sendAmount` is no longer valid. */ - expiresAt?: string; - /** - * Format: date-time - * @description The date and time when the quote was created. - */ - createdAt: string; - }; - /** - * Amount - * @description All amounts in open payments are represented as a value and an asset code and scale. - * - * The `value` is an unsigned 64-bit integer amount, represented as a string. - * - * The `assetCode` is a code that indicates the underlying asset. In most cases this SHOULD be a 3-character ISO 4217 currency code. - * - * The `assetScale` indicates how the `value` has been scaled relative to the natural scale of the asset. For example, an `value` of `"1234"` with an `assetScale` of `2` represents an amount of 12.34. - */ - amount: { - /** - * Format: uint64 - * @description The amount, scaled by the given scale. - */ - value: string; - /** @description The asset code of the amount. */ - assetCode: components["schemas"]["assetCode"]; - /** @description The scale of the amount. */ - assetScale: components["schemas"]["assetScale"]; - }; - /** - * Asset code - * @description This SHOULD be an ISO4217 currency code. - */ - assetCode: string; - /** - * Asset scale - * @description The scale of amounts denoted in the corresponding asset code. - */ - assetScale: number; - /** - * Receiver - * Format: uri - * @description The URL of the incoming payment or ILP STREAM connection that is being paid. - */ - receiver: string; - /** @description Pagination parameters */ - pagination: - | components["schemas"]["forward-pagination"] - | components["schemas"]["backward-pagination"]; - /** @description Forward pagination parameters */ - "forward-pagination": { - /** @description The number of items to return. */ - first?: number; - /** @description The cursor key to list from. */ - cursor?: string; - }; - /** @description Backward pagination parameters */ - "backward-pagination": { - /** @description The number of items to return. */ - last?: number; - /** @description The cursor key to list from. */ - cursor: string; - }; - "page-info": { - /** @description Cursor corresponding to the first element in the result array. */ - startCursor: string; - /** @description Cursor corresponding to the last element in the result array. */ - endCursor: string; - /** @description Describes whether the data set has further entries. */ - hasNextPage: boolean; - /** @description Describes whether the data set has previous entries. */ - hasPreviousPage: boolean; - }; - }; - responses: { - /** Authorization required */ - 401: unknown; - /** Forbidden */ - 403: unknown; - }; - parameters: { - /** @description Sub-resource identifier */ - id: string; - /** @description The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - signature: string; - /** @description The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "signature-input": string; - }; -} - -export interface operations { - /** - * Retrieve the public information of the Payment Pointer. - * - * This end-point should be open to anonymous requests as it allows clients to verify a Payment Pointer URL and get the basic information required to construct new transactions and discover the grant request URL. - * - * The content should be slow changing and cacheable for long periods. Servers SHOULD use cache control headers. - */ - "get-payment-pointer": { - responses: { - /** Payment Pointer Found */ - 200: { - content: { - "application/json": components["schemas"]["payment-pointer"]; - }; - }; - /** Payment Pointer Not Found */ - 404: unknown; - }; - }; - /** - * *NB* Use server url specific to this path. - * - * Fetch new connection credentials for an ILP STREAM connection. - * - * A connection is an ephemeral resource that is created to accommodate new incoming payments. - * - * A new set of credential will be generated each time this API is called. - */ - "get-ilp-stream-connection": { - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - }; - responses: { - /** Connection Found */ - 200: { - content: { - "application/json": components["schemas"]["ilp-stream-connection"]; - }; - }; - /** Connection Not Found */ - 404: unknown; - }; - }; - /** List all incoming payments on the payment pointer */ - "list-incoming-payments": { - parameters: { - query: { - /** Pagination parameters */ - pagination?: components["schemas"]["pagination"]; - }; - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** OK */ - 200: { - content: { - "application/json": { - pagination?: components["schemas"]["page-info"]; - result?: components["schemas"]["incoming-payment-with-connection-url"][]; - }; - }; - }; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - }; - }; - /** - * A client MUST create an **incoming payment** resource before it is possible to send any payments to the payment pointer. - * - * All of the input parameters are _optional_. - */ - "create-incoming-payment": { - parameters: { - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** Incoming Payment Created */ - 201: { - content: { - "application/json": components["schemas"]["incoming-payment-with-connection"]; - }; - }; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - }; - /** - * A subset of the incoming payments schema is accepted as input to create a new incoming payment. - * - * The `incomingAmount` must use the same `assetCode` and `assetScale` as the payment pointer. - */ - requestBody: { - content: { - "application/json": { - /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ - incomingAmount?: components["schemas"]["amount"]; - /** - * Format: date-time - * @description The date and time when payments into the incoming payment must no longer be accepted. - */ - expiresAt?: string; - /** @description Human readable description of the incoming payment that will be visible to the account holder. */ - description?: string; - /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ - externalRef?: string; - }; - }; - }; - }; - /** List all outgoing payments on the payment pointer */ - "list-outgoing-payments": { - parameters: { - query: { - /** Pagination parameters */ - pagination?: components["schemas"]["pagination"]; - }; - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** OK */ - 200: { - content: { - "application/json": { - pagination?: components["schemas"]["page-info"]; - result?: components["schemas"]["outgoing-payment"][]; - }; - }; - }; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - }; - }; - /** An **outgoing payment** is a sub-resource of a payment pointer. It represents a payment from the payment pointer. */ - "create-outgoing-payment": { - parameters: { - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** Outgoing Payment Created */ - 201: { - content: { - "application/json": components["schemas"]["outgoing-payment"]; - }; - }; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - }; - /** - * A subset of the outgoing payments schema is accepted as input to create a new outgoing payment. - * - * The `sendAmount` must use the same `assetCode` and `assetScale` as the payment pointer. - */ - requestBody: { - content: { - "application/json": { - /** - * Format: uri - * @description The URL of the quote defining this payment's amounts. - */ - quoteId: string; - /** @description Human readable description of the outgoing payment that will be visible to the account holder and shared with the receiver. */ - description?: string; - /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ - externalRef?: string; - }; - }; - }; - }; - /** A **quote** is a sub-resource of a payment pointer. It represents a quote for a payment from the payment pointer. */ - "create-quote": { - parameters: { - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** Quote Created */ - 201: { - content: { - "application/json": components["schemas"]["quote"]; - }; - }; - /** No amount was provided and no amount could be inferred from the receiver. */ - 400: unknown; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - }; - /** - * A subset of the quotes schema is accepted as input to create a new quote. - * - * The quote must be created with a (`sendAmount` xor `receiveAmount`) unless the `receiver` is an Incoming Payment which has an `incomingAmount`. - */ - requestBody: { - content: { - "application/json": { - receiver: components["schemas"]["receiver"]; - receiveAmount?: components["schemas"]["amount"]; - sendAmount?: components["schemas"]["amount"]; - }; - }; - }; - }; - /** A client can fetch the latest state of an incoming payment to determine the amount received into the payment pointer. */ - "get-incoming-payment": { - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** Incoming Payment Found */ - 200: { - content: { - "application/json": components["schemas"]["incoming-payment-with-connection"]; - }; - }; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - /** Incoming Payment Not Found */ - 404: unknown; - }; - }; - /** - * A client with the appropriate permissions MAY mark a non-expired **incoming payment** as `completed` if it has not yet received `incomingAmount`. - * - * This indicates to the receiving account provider that it can begin any post processing of the payment such as generating account statements or notifying the account holder of the completed payment. - */ - "complete-incoming-payment": { - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** OK */ - 200: { - content: { - "application/json": components["schemas"]["incoming-payment"]; - }; - }; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - /** Incoming Payment Not Found */ - 404: unknown; - }; - }; - /** A client can fetch the latest state of an outgoing payment. */ - "get-outgoing-payment": { - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** Outgoing Payment Found */ - 200: { - content: { - "application/json": components["schemas"]["outgoing-payment"]; - }; - }; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - /** Outgoing Payment Not Found */ - 404: unknown; - }; - }; - /** A client can fetch the latest state of a quote. */ - "get-quote": { - parameters: { - path: { - /** Sub-resource identifier */ - id: components["parameters"]["id"]; - }; - header: { - /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ - "Signature-Input": components["parameters"]["signature-input"]; - /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ - Signature: components["parameters"]["signature"]; - }; - }; - responses: { - /** Quote Found */ - 200: { - content: { - "application/json": components["schemas"]["quote"]; - }; - }; - 401: components["responses"]["401"]; - 403: components["responses"]["403"]; - /** Quote Not Found */ - 404: unknown; - }; - }; -} - -export interface external {} diff --git a/packages/open-payments-client/tsconfig.json b/packages/open-payments-client/tsconfig.json deleted file mode 100644 index c7e406b567..0000000000 --- a/packages/open-payments-client/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.build.json", - "compilerOptions": { - "lib": ["ES2020"], - "outDir": "./dist", - "rootDir": "./src", - "declaration": true - }, - "include": ["src/**/*"], - "exclude": ["**/*.test.ts"] -} From 32e2a315a5d250847e78cb909a451bf8cda14b1a Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Thu, 13 Oct 2022 17:54:40 +0200 Subject: [PATCH 04/33] feat(open-payments): make open-payments folder and use script for type generation --- packages/open-payments/config.js | 5 + packages/open-payments/package.json | 25 + .../open-payments/scripts/generate-types.js | 19 + packages/open-payments/src/client.ts | 57 ++ packages/open-payments/src/generated/types.ts | 708 ++++++++++++++++++ packages/open-payments/src/index.ts | 2 + packages/open-payments/src/types.ts | 4 + packages/open-payments/tsconfig.json | 11 + 8 files changed, 831 insertions(+) create mode 100644 packages/open-payments/config.js create mode 100644 packages/open-payments/package.json create mode 100644 packages/open-payments/scripts/generate-types.js create mode 100644 packages/open-payments/src/client.ts create mode 100644 packages/open-payments/src/generated/types.ts create mode 100644 packages/open-payments/src/index.ts create mode 100644 packages/open-payments/src/types.ts create mode 100644 packages/open-payments/tsconfig.json diff --git a/packages/open-payments/config.js b/packages/open-payments/config.js new file mode 100644 index 0000000000..f277636b12 --- /dev/null +++ b/packages/open-payments/config.js @@ -0,0 +1,5 @@ +export default { + OPEN_PAYMENTS_OPEN_API_URL: + 'https://raw.githubusercontent.com/interledger/open-payments/main/open-api-spec.yaml', + DEFAULT_REQUEST_TIMEOUT: 3_000 +} diff --git a/packages/open-payments/package.json b/packages/open-payments/package.json new file mode 100644 index 0000000000..4881c517a9 --- /dev/null +++ b/packages/open-payments/package.json @@ -0,0 +1,25 @@ +{ + "name": "open-payments", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "module", + "files": [ + "dist/**/*" + ], + "scripts": { + "build": "pnpm clean && tsc --build tsconfig.json", + "clean": "rm -fr dist/", + "prepack": "pnpm build", + "generate:types": "node scripts/generate-types.js" + }, + "devDependencies": { + "@types/node": "^18.7.12", + "openapi-typescript": "^5.4.1", + "typescript": "^4.2.4" + }, + "dependencies": { + "axios": "^1.1.2" + } +} diff --git a/packages/open-payments/scripts/generate-types.js b/packages/open-payments/scripts/generate-types.js new file mode 100644 index 0000000000..0ff22c9f2d --- /dev/null +++ b/packages/open-payments/scripts/generate-types.js @@ -0,0 +1,19 @@ +import fs from 'fs' +import openapiTS from 'openapi-typescript' +import config from '../config.js' +;(async () => { + try { + const output = await openapiTS(new URL(config.OPEN_PAYMENTS_OPEN_API_URL)) + const fileName = 'src/generated/types.ts' + + fs.writeFile(fileName, output, (error) => { + if (error) { + console.log(`Error when writing types to ${fileName}`, { error }) + } + }) + } catch (error) { + console.log('Error when generating types', { + error + }) + } +})() diff --git a/packages/open-payments/src/client.ts b/packages/open-payments/src/client.ts new file mode 100644 index 0000000000..83bcd264e4 --- /dev/null +++ b/packages/open-payments/src/client.ts @@ -0,0 +1,57 @@ +import { ILPStreamConnection, IncomingPayment } from './types' +import axios, { AxiosInstance } from 'axios' +import config from '../config.js' + +interface CreateOpenPaymentClientArgs { + timeout?: number +} + +interface GetArgs { + url: string + accessToken: string +} + +export interface OpenPaymentsClient { + incomingPayment: { + get(args: GetArgs): Promise + } + ilpStreamConnection: { + get(args: GetArgs): Promise + } +} + +const get = async (axios: AxiosInstance, args: GetArgs): Promise => { + const { url, accessToken } = args + + const { data } = await axios.get(url, { + headers: accessToken + ? { + Authorization: `GNAP ${accessToken}`, + Signature: 'TODO', + 'Signature-Input': 'TODO' + } + : {} + }) + + return data +} + +export const createClient = ( + args?: CreateOpenPaymentClientArgs +): OpenPaymentsClient => { + const defaultTimeout = config['DEFAULT_REQUEST_TIMEOUT'] || 3_000 + const axiosInstance = axios.create({ + timeout: args.timeout || defaultTimeout + }) + + axiosInstance.defaults.headers.common['Content-Type'] = 'application/json' + + return { + incomingPayment: { + get: (args: GetArgs) => get(axios, args) + }, + ilpStreamConnection: { + get: (args: GetArgs) => get(axios, args) + } + } +} diff --git a/packages/open-payments/src/generated/types.ts b/packages/open-payments/src/generated/types.ts new file mode 100644 index 0000000000..30fa869425 --- /dev/null +++ b/packages/open-payments/src/generated/types.ts @@ -0,0 +1,708 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/": { + /** + * Retrieve the public information of the Payment Pointer. + * + * This end-point should be open to anonymous requests as it allows clients to verify a Payment Pointer URL and get the basic information required to construct new transactions and discover the grant request URL. + * + * The content should be slow changing and cacheable for long periods. Servers SHOULD use cache control headers. + */ + get: operations["get-payment-pointer"]; + }; + "/connections/{id}": { + /** + * *NB* Use server url specific to this path. + * + * Fetch new connection credentials for an ILP STREAM connection. + * + * A connection is an ephemeral resource that is created to accommodate new incoming payments. + * + * A new set of credential will be generated each time this API is called. + */ + get: operations["get-ilp-stream-connection"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; + "/incoming-payments": { + /** List all incoming payments on the payment pointer */ + get: operations["list-incoming-payments"]; + /** + * A client MUST create an **incoming payment** resource before it is possible to send any payments to the payment pointer. + * + * All of the input parameters are _optional_. + */ + post: operations["create-incoming-payment"]; + }; + /** Create a new outgoing payment at the payment pointer. */ + "/outgoing-payments": { + /** List all outgoing payments on the payment pointer */ + get: operations["list-outgoing-payments"]; + /** An **outgoing payment** is a sub-resource of a payment pointer. It represents a payment from the payment pointer. */ + post: operations["create-outgoing-payment"]; + }; + /** Create a new quote at the payment pointer. */ + "/quotes": { + /** A **quote** is a sub-resource of a payment pointer. It represents a quote for a payment from the payment pointer. */ + post: operations["create-quote"]; + }; + "/incoming-payments/{id}": { + /** A client can fetch the latest state of an incoming payment to determine the amount received into the payment pointer. */ + get: operations["get-incoming-payment"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; + "/incoming-payments/{id}/complete": { + /** + * A client with the appropriate permissions MAY mark a non-expired **incoming payment** as `completed` if it has not yet received `incomingAmount`. + * + * This indicates to the receiving account provider that it can begin any post processing of the payment such as generating account statements or notifying the account holder of the completed payment. + */ + post: operations["complete-incoming-payment"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; + "/outgoing-payments/{id}": { + /** A client can fetch the latest state of an outgoing payment. */ + get: operations["get-outgoing-payment"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; + "/quotes/{id}": { + /** A client can fetch the latest state of a quote. */ + get: operations["get-quote"]; + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + }; +} + +export interface components { + schemas: { + /** + * Payment Pointer + * @description A **payment pointer** resource is the root of the API and contains the public details of the financial account represented by the Payment Pointer that is also the service endpoint URL. + */ + "payment-pointer": { + /** + * Format: uri + * @description The URL identifying the incoming payment. + */ + id: string; + /** @description A public name for the account. This should be set by the account holder with their provider to provide a hint to counterparties as to the identity of the account holder. */ + publicName?: string; + /** @description The asset code of the account. */ + assetCode: components["schemas"]["assetCode"]; + assetScale: components["schemas"]["assetScale"]; + /** + * Format: uri + * @description The URL of the authorization server endpoint for getting grants and access tokens for this payment pointer. + */ + authServer: string; + }; + /** + * ILP Stream Connection + * @description An **ILP STREAM Connection** is an endpoint that returns unique STREAM connection credentials to establish a STREAM connection to the underlying account. + */ + "ilp-stream-connection": { + /** + * Format: uri + * @description The URL identifying the endpoint. + */ + id: string; + /** @description The ILP address to use when establishing a STREAM connection. */ + ilpAddress: string; + /** @description The base64 url-encoded shared secret to use when establishing a STREAM connection. */ + sharedSecret: string; + /** @description The asset code of the amount. */ + assetCode: components["schemas"]["assetCode"]; + /** @description The scale of the amount. */ + assetScale: components["schemas"]["assetScale"]; + }; + /** + * Incoming Payment + * @description An **incoming payment** resource represents a payment that will be, is currently being, or has been received by the account. + */ + "incoming-payment": { + /** + * Format: uri + * @description The URL identifying the incoming payment. + */ + id: string; + /** + * Format: uri + * @description The URL of the payment pointer this payment is being made into. + */ + paymentPointer: string; + /** + * @description Describes whether the incoming payment has completed receiving fund. + * @default false + */ + completed: boolean; + /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ + incomingAmount?: components["schemas"]["amount"]; + /** @description The total amount that has been paid into the payment pointer under this incoming payment. */ + receivedAmount: components["schemas"]["amount"]; + /** + * Format: date-time + * @description The date and time when payments under this incoming payment will no longer be accepted. + */ + expiresAt?: string; + /** @description Human readable description of the incoming payment that will be visible to the account holder. */ + description?: string; + /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. */ + externalRef?: string; + /** + * Format: date-time + * @description The date and time when the incoming payment was created. + */ + createdAt: string; + /** + * Format: date-time + * @description The date and time when the incoming payment was updated. + */ + updatedAt: string; + }; + /** + * Incoming Payment with Connection + * @description An **incoming payment** resource with the Interledger STREAM Connection to use to pay into the payment pointer under this incoming payment. + */ + "incoming-payment-with-connection": components["schemas"]["incoming-payment"] & { + ilpStreamConnection?: components["schemas"]["ilp-stream-connection"]; + }; + /** + * Incoming Payment with Connection + * @description An **incoming payment** resource with the url for the Interledger STREAM Connection resource to use to pay into the payment pointer under this incoming payment. + */ + "incoming-payment-with-connection-url": components["schemas"]["incoming-payment"] & { + /** + * Format: uri + * @description Endpoint that returns unique STREAM connection credentials to establish a STREAM connection to the underlying account. + */ + ilpStreamConnection?: string; + }; + /** + * Outgoing Payment + * @description An **outgoing payment** resource represents a payment that will be, is currently being, or has previously been, sent from the payment pointer. + */ + "outgoing-payment": { + /** + * Format: uri + * @description The URL identifying the outgoing payment. + */ + id: string; + /** + * Format: uri + * @description The URL of the payment pointer from which this payment is sent. + */ + paymentPointer: string; + /** + * Format: uri + * @description The URL of the quote defining this payment's amounts. + */ + quoteId?: string; + /** + * @description Describes whether the payment failed to send its full amount. + * @default false + */ + failed?: boolean; + /** @description The URL of the incoming payment or ILP STREAM Connection that is being paid. */ + receiver: components["schemas"]["receiver"]; + /** @description The total amount that should be received by the receiver when this outgoing payment has been paid. */ + receiveAmount: components["schemas"]["amount"]; + /** @description The total amount that should be sent when this outgoing payment has been paid. */ + sendAmount: components["schemas"]["amount"]; + /** @description The total amount that has been sent under this outgoing payment. */ + sentAmount: components["schemas"]["amount"]; + /** @description Human readable description of the outgoing payment that will be visible to the account holder and shared with the receiver. */ + description?: string; + /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ + externalRef?: string; + /** + * Format: date-time + * @description The date and time when the outgoing payment was created. + */ + createdAt: string; + /** + * Format: date-time + * @description The date and time when the outgoing payment was updated. + */ + updatedAt: string; + }; + /** + * Quote + * @description A **quote** resource represents the quoted amount details with which an Outgoing Payment may be created. + */ + quote: { + /** + * Format: uri + * @description The URL identifying the quote. + */ + id: string; + /** + * Format: uri + * @description The URL of the payment pointer from which this quote's payment would be sent. + */ + paymentPointer: string; + /** @description The URL of the incoming payment or ILP Stream Connection that would be paid. */ + receiver: components["schemas"]["receiver"]; + /** @description The total amount that should be received by the receiver. */ + receiveAmount: components["schemas"]["amount"]; + /** @description The total amount that should be sent by the sender. */ + sendAmount: components["schemas"]["amount"]; + /** @description The date and time when the calculated `sendAmount` is no longer valid. */ + expiresAt?: string; + /** + * Format: date-time + * @description The date and time when the quote was created. + */ + createdAt: string; + }; + /** + * Amount + * @description All amounts in open payments are represented as a value and an asset code and scale. + * + * The `value` is an unsigned 64-bit integer amount, represented as a string. + * + * The `assetCode` is a code that indicates the underlying asset. In most cases this SHOULD be a 3-character ISO 4217 currency code. + * + * The `assetScale` indicates how the `value` has been scaled relative to the natural scale of the asset. For example, an `value` of `"1234"` with an `assetScale` of `2` represents an amount of 12.34. + */ + amount: { + /** + * Format: uint64 + * @description The amount, scaled by the given scale. + */ + value: string; + /** @description The asset code of the amount. */ + assetCode: components["schemas"]["assetCode"]; + /** @description The scale of the amount. */ + assetScale: components["schemas"]["assetScale"]; + }; + /** + * Asset code + * @description This SHOULD be an ISO4217 currency code. + */ + assetCode: string; + /** + * Asset scale + * @description The scale of amounts denoted in the corresponding asset code. + */ + assetScale: number; + /** + * Receiver + * Format: uri + * @description The URL of the incoming payment or ILP STREAM connection that is being paid. + */ + receiver: string; + /** @description Pagination parameters */ + pagination: + | components["schemas"]["forward-pagination"] + | components["schemas"]["backward-pagination"]; + /** @description Forward pagination parameters */ + "forward-pagination": { + /** @description The number of items to return. */ + first?: number; + /** @description The cursor key to list from. */ + cursor?: string; + }; + /** @description Backward pagination parameters */ + "backward-pagination": { + /** @description The number of items to return. */ + last?: number; + /** @description The cursor key to list from. */ + cursor: string; + }; + "page-info": { + /** @description Cursor corresponding to the first element in the result array. */ + startCursor: string; + /** @description Cursor corresponding to the last element in the result array. */ + endCursor: string; + /** @description Describes whether the data set has further entries. */ + hasNextPage: boolean; + /** @description Describes whether the data set has previous entries. */ + hasPreviousPage: boolean; + }; + }; + responses: { + /** Authorization required */ + 401: unknown; + /** Forbidden */ + 403: unknown; + }; + parameters: { + /** @description Sub-resource identifier */ + id: string; + /** @description The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + signature: string; + /** @description The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "signature-input": string; + }; +} + +export interface operations { + /** + * Retrieve the public information of the Payment Pointer. + * + * This end-point should be open to anonymous requests as it allows clients to verify a Payment Pointer URL and get the basic information required to construct new transactions and discover the grant request URL. + * + * The content should be slow changing and cacheable for long periods. Servers SHOULD use cache control headers. + */ + "get-payment-pointer": { + responses: { + /** Payment Pointer Found */ + 200: { + content: { + "application/json": components["schemas"]["payment-pointer"]; + }; + }; + /** Payment Pointer Not Found */ + 404: unknown; + }; + }; + /** + * *NB* Use server url specific to this path. + * + * Fetch new connection credentials for an ILP STREAM connection. + * + * A connection is an ephemeral resource that is created to accommodate new incoming payments. + * + * A new set of credential will be generated each time this API is called. + */ + "get-ilp-stream-connection": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + }; + responses: { + /** Connection Found */ + 200: { + content: { + "application/json": components["schemas"]["ilp-stream-connection"]; + }; + }; + /** Connection Not Found */ + 404: unknown; + }; + }; + /** List all incoming payments on the payment pointer */ + "list-incoming-payments": { + parameters: { + query: { + /** Pagination parameters */ + pagination?: components["schemas"]["pagination"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": { + pagination?: components["schemas"]["page-info"]; + result?: components["schemas"]["incoming-payment-with-connection-url"][]; + }; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + }; + /** + * A client MUST create an **incoming payment** resource before it is possible to send any payments to the payment pointer. + * + * All of the input parameters are _optional_. + */ + "create-incoming-payment": { + parameters: { + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Incoming Payment Created */ + 201: { + content: { + "application/json": components["schemas"]["incoming-payment-with-connection"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + /** + * A subset of the incoming payments schema is accepted as input to create a new incoming payment. + * + * The `incomingAmount` must use the same `assetCode` and `assetScale` as the payment pointer. + */ + requestBody: { + content: { + "application/json": { + /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ + incomingAmount?: components["schemas"]["amount"]; + /** + * Format: date-time + * @description The date and time when payments into the incoming payment must no longer be accepted. + */ + expiresAt?: string; + /** @description Human readable description of the incoming payment that will be visible to the account holder. */ + description?: string; + /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ + externalRef?: string; + }; + }; + }; + }; + /** List all outgoing payments on the payment pointer */ + "list-outgoing-payments": { + parameters: { + query: { + /** Pagination parameters */ + pagination?: components["schemas"]["pagination"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": { + pagination?: components["schemas"]["page-info"]; + result?: components["schemas"]["outgoing-payment"][]; + }; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + }; + /** An **outgoing payment** is a sub-resource of a payment pointer. It represents a payment from the payment pointer. */ + "create-outgoing-payment": { + parameters: { + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Outgoing Payment Created */ + 201: { + content: { + "application/json": components["schemas"]["outgoing-payment"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + /** + * A subset of the outgoing payments schema is accepted as input to create a new outgoing payment. + * + * The `sendAmount` must use the same `assetCode` and `assetScale` as the payment pointer. + */ + requestBody: { + content: { + "application/json": { + /** + * Format: uri + * @description The URL of the quote defining this payment's amounts. + */ + quoteId: string; + /** @description Human readable description of the outgoing payment that will be visible to the account holder and shared with the receiver. */ + description?: string; + /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ + externalRef?: string; + }; + }; + }; + }; + /** A **quote** is a sub-resource of a payment pointer. It represents a quote for a payment from the payment pointer. */ + "create-quote": { + parameters: { + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Quote Created */ + 201: { + content: { + "application/json": components["schemas"]["quote"]; + }; + }; + /** No amount was provided and no amount could be inferred from the receiver. */ + 400: unknown; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + }; + /** + * A subset of the quotes schema is accepted as input to create a new quote. + * + * The quote must be created with a (`sendAmount` xor `receiveAmount`) unless the `receiver` is an Incoming Payment which has an `incomingAmount`. + */ + requestBody: { + content: { + "application/json": { + receiver: components["schemas"]["receiver"]; + receiveAmount?: components["schemas"]["amount"]; + sendAmount?: components["schemas"]["amount"]; + }; + }; + }; + }; + /** A client can fetch the latest state of an incoming payment to determine the amount received into the payment pointer. */ + "get-incoming-payment": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Incoming Payment Found */ + 200: { + content: { + "application/json": components["schemas"]["incoming-payment-with-connection"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + /** Incoming Payment Not Found */ + 404: unknown; + }; + }; + /** + * A client with the appropriate permissions MAY mark a non-expired **incoming payment** as `completed` if it has not yet received `incomingAmount`. + * + * This indicates to the receiving account provider that it can begin any post processing of the payment such as generating account statements or notifying the account holder of the completed payment. + */ + "complete-incoming-payment": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** OK */ + 200: { + content: { + "application/json": components["schemas"]["incoming-payment"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + /** Incoming Payment Not Found */ + 404: unknown; + }; + }; + /** A client can fetch the latest state of an outgoing payment. */ + "get-outgoing-payment": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Outgoing Payment Found */ + 200: { + content: { + "application/json": components["schemas"]["outgoing-payment"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + /** Outgoing Payment Not Found */ + 404: unknown; + }; + }; + /** A client can fetch the latest state of a quote. */ + "get-quote": { + parameters: { + path: { + /** Sub-resource identifier */ + id: components["parameters"]["id"]; + }; + header: { + /** The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member's key is the label that uniquely identifies the message signature within the context of the HTTP message. The member's value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization" When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details. */ + "Signature-Input": components["parameters"]["signature-input"]; + /** The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK. */ + Signature: components["parameters"]["signature"]; + }; + }; + responses: { + /** Quote Found */ + 200: { + content: { + "application/json": components["schemas"]["quote"]; + }; + }; + 401: components["responses"]["401"]; + 403: components["responses"]["403"]; + /** Quote Not Found */ + 404: unknown; + }; + }; +} + +export interface external {} diff --git a/packages/open-payments/src/index.ts b/packages/open-payments/src/index.ts new file mode 100644 index 0000000000..8e8c16c9bf --- /dev/null +++ b/packages/open-payments/src/index.ts @@ -0,0 +1,2 @@ +export * from './types' +export { createClient, OpenPaymentsClient } from './client' diff --git a/packages/open-payments/src/types.ts b/packages/open-payments/src/types.ts new file mode 100644 index 0000000000..a282d9b169 --- /dev/null +++ b/packages/open-payments/src/types.ts @@ -0,0 +1,4 @@ +import { components } from './generated/types' + +export type IncomingPayment = components['schemas']['incoming-payment'] +export type ILPStreamConnection = components['schemas']['ilp-stream-connection'] diff --git a/packages/open-payments/tsconfig.json b/packages/open-payments/tsconfig.json new file mode 100644 index 0000000000..b6b5f0ce92 --- /dev/null +++ b/packages/open-payments/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "lib": ["ES2020"], + "outDir": "./dist", + // "rootDir": "./src", + "declaration": true + }, + "include": ["src/**/*"], + "exclude": ["**/*.test.ts"] +} From 050ebefd97e8b7181d077a2c59633b2e896abb99 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Thu, 13 Oct 2022 17:55:37 +0200 Subject: [PATCH 05/33] feat(open-payments): fix rootDir --- packages/open-payments/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/open-payments/tsconfig.json b/packages/open-payments/tsconfig.json index b6b5f0ce92..c7e406b567 100644 --- a/packages/open-payments/tsconfig.json +++ b/packages/open-payments/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": ["ES2020"], "outDir": "./dist", - // "rootDir": "./src", + "rootDir": "./src", "declaration": true }, "include": ["src/**/*"], From 989302902ed8dd61afdabb56733fc09638f54eb4 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 14:35:26 +0200 Subject: [PATCH 06/33] feat(open-payments): downgrading generation lib, updating how script is ran --- packages/open-payments/package.json | 9 ++++----- .../scripts/{generate-types.js => generate-types.ts} | 4 ++-- packages/open-payments/src/client.ts | 6 +++--- packages/open-payments/{config.js => src/config.ts} | 0 packages/open-payments/src/generated/types.ts | 10 ++-------- packages/open-payments/tsconfig.json | 2 +- 6 files changed, 12 insertions(+), 19 deletions(-) rename packages/open-payments/scripts/{generate-types.js => generate-types.ts} (77%) rename packages/open-payments/{config.js => src/config.ts} (100%) diff --git a/packages/open-payments/package.json b/packages/open-payments/package.json index 4881c517a9..ea1c7a6dae 100644 --- a/packages/open-payments/package.json +++ b/packages/open-payments/package.json @@ -1,10 +1,8 @@ { "name": "open-payments", - "version": "1.0.0", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", - "type": "module", "files": [ "dist/**/*" ], @@ -12,12 +10,13 @@ "build": "pnpm clean && tsc --build tsconfig.json", "clean": "rm -fr dist/", "prepack": "pnpm build", - "generate:types": "node scripts/generate-types.js" + "generate:types": "npx ts-node scripts/generate-types.ts" }, "devDependencies": { "@types/node": "^18.7.12", - "openapi-typescript": "^5.4.1", - "typescript": "^4.2.4" + "openapi-typescript": "^4.5.0", + "ts-node": "^10.7.0", + "typescript": "^4.3.0" }, "dependencies": { "axios": "^1.1.2" diff --git a/packages/open-payments/scripts/generate-types.js b/packages/open-payments/scripts/generate-types.ts similarity index 77% rename from packages/open-payments/scripts/generate-types.js rename to packages/open-payments/scripts/generate-types.ts index 0ff22c9f2d..27c978a180 100644 --- a/packages/open-payments/scripts/generate-types.js +++ b/packages/open-payments/scripts/generate-types.ts @@ -1,9 +1,9 @@ import fs from 'fs' import openapiTS from 'openapi-typescript' -import config from '../config.js' +import config from '../src/config' ;(async () => { try { - const output = await openapiTS(new URL(config.OPEN_PAYMENTS_OPEN_API_URL)) + const output = await openapiTS(config.OPEN_PAYMENTS_OPEN_API_URL) const fileName = 'src/generated/types.ts' fs.writeFile(fileName, output, (error) => { diff --git a/packages/open-payments/src/client.ts b/packages/open-payments/src/client.ts index 83bcd264e4..84d030d271 100644 --- a/packages/open-payments/src/client.ts +++ b/packages/open-payments/src/client.ts @@ -1,6 +1,6 @@ import { ILPStreamConnection, IncomingPayment } from './types' import axios, { AxiosInstance } from 'axios' -import config from '../config.js' +import config from './config' interface CreateOpenPaymentClientArgs { timeout?: number @@ -39,9 +39,9 @@ const get = async (axios: AxiosInstance, args: GetArgs): Promise => { export const createClient = ( args?: CreateOpenPaymentClientArgs ): OpenPaymentsClient => { - const defaultTimeout = config['DEFAULT_REQUEST_TIMEOUT'] || 3_000 + const defaultTimeout = config.DEFAULT_REQUEST_TIMEOUT || 3_000 const axiosInstance = axios.create({ - timeout: args.timeout || defaultTimeout + timeout: args.timeout ?? defaultTimeout }) axiosInstance.defaults.headers.common['Content-Type'] = 'application/json' diff --git a/packages/open-payments/config.js b/packages/open-payments/src/config.ts similarity index 100% rename from packages/open-payments/config.js rename to packages/open-payments/src/config.ts diff --git a/packages/open-payments/src/generated/types.ts b/packages/open-payments/src/generated/types.ts index 30fa869425..95973a957a 100644 --- a/packages/open-payments/src/generated/types.ts +++ b/packages/open-payments/src/generated/types.ts @@ -157,10 +157,7 @@ export interface components { * @description The URL of the payment pointer this payment is being made into. */ paymentPointer: string; - /** - * @description Describes whether the incoming payment has completed receiving fund. - * @default false - */ + /** @description Describes whether the incoming payment has completed receiving fund. */ completed: boolean; /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ incomingAmount?: components["schemas"]["amount"]; @@ -224,10 +221,7 @@ export interface components { * @description The URL of the quote defining this payment's amounts. */ quoteId?: string; - /** - * @description Describes whether the payment failed to send its full amount. - * @default false - */ + /** @description Describes whether the payment failed to send its full amount. */ failed?: boolean; /** @description The URL of the incoming payment or ILP STREAM Connection that is being paid. */ receiver: components["schemas"]["receiver"]; diff --git a/packages/open-payments/tsconfig.json b/packages/open-payments/tsconfig.json index c7e406b567..cf98f1692c 100644 --- a/packages/open-payments/tsconfig.json +++ b/packages/open-payments/tsconfig.json @@ -7,5 +7,5 @@ "declaration": true }, "include": ["src/**/*"], - "exclude": ["**/*.test.ts"] + "exclude": ["**/*.test.ts", "src/scripts/*"] } From c15fdbc418b695aa317c6c21a893bd48675f3150 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 14:36:49 +0200 Subject: [PATCH 07/33] feat(open-payments): update default config usage --- packages/open-payments/src/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/open-payments/src/client.ts b/packages/open-payments/src/client.ts index 84d030d271..0e882b8bf5 100644 --- a/packages/open-payments/src/client.ts +++ b/packages/open-payments/src/client.ts @@ -39,9 +39,8 @@ const get = async (axios: AxiosInstance, args: GetArgs): Promise => { export const createClient = ( args?: CreateOpenPaymentClientArgs ): OpenPaymentsClient => { - const defaultTimeout = config.DEFAULT_REQUEST_TIMEOUT || 3_000 const axiosInstance = axios.create({ - timeout: args.timeout ?? defaultTimeout + timeout: args.timeout ?? config.DEFAULT_REQUEST_TIMEOUT }) axiosInstance.defaults.headers.common['Content-Type'] = 'application/json' From 130e3105c1105f556a10afcad2cc7097baf53252 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 14:47:23 +0200 Subject: [PATCH 08/33] feat(open-payments): update pnpm-lock.yaml --- pnpm-lock.yaml | 164 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 147 insertions(+), 17 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b35775d3bc..cff2e1ee27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,10 +50,10 @@ importers: '@koa/router': ^12.0.0 '@types/jest': ^28.1.7 '@types/koa': 2.13.5 - '@types/koa-bodyparser': ^4.3.7 - '@types/koa-session': ^5.10.6 '@types/koa__cors': ^3.1.1 '@types/koa__router': ^8.0.11 + '@types/koa-bodyparser': ^4.3.7 + '@types/koa-session': ^5.10.6 '@types/uuid': ^8.3.4 ajv: ^8.11.0 axios: ^0.27.2 @@ -98,10 +98,10 @@ importers: devDependencies: '@types/jest': 28.1.7 '@types/koa': 2.13.5 - '@types/koa-bodyparser': 4.3.7 - '@types/koa-session': 5.10.6 '@types/koa__cors': 3.3.0 '@types/koa__router': 8.0.11 + '@types/koa-bodyparser': 4.3.7 + '@types/koa-session': 5.10.6 '@types/uuid': 8.3.4 jest-openapi: 0.14.2 nock: 13.2.9 @@ -129,9 +129,9 @@ importers: '@types/bcrypt': ^5.0.0 '@types/jest': ^28.1.7 '@types/koa': 2.13.5 - '@types/koa-bodyparser': ^4.3.7 '@types/koa__cors': ^3.0.2 '@types/koa__router': ^8.0.11 + '@types/koa-bodyparser': ^4.3.7 '@types/lodash': ^4.14.184 '@types/luxon': ^3.0.0 '@types/react': ^18.0.17 @@ -170,6 +170,7 @@ importers: objection: ^3.0.1 objection-db-errors: ^1.1.2 oer-utils: 5.1.3-alpha.1 + open-payments: workspace:../open-payments openapi: workspace:../openapi openapi-types: ^12.0.0 pg: ^8.6.0 @@ -222,6 +223,7 @@ importers: objection: 3.0.1_knex@0.95.15 objection-db-errors: 1.1.2_objection@3.0.1 oer-utils: 5.1.3-alpha.1 + open-payments: link:../open-payments openapi: link:../openapi pg: 8.7.3 pino: 8.4.2 @@ -236,9 +238,9 @@ importers: '@graphql-codegen/typescript-resolvers': 2.7.3_graphql@16.6.0 '@types/jest': 28.1.7 '@types/koa': 2.13.5 - '@types/koa-bodyparser': 4.3.7 '@types/koa__cors': 3.3.0 '@types/koa__router': 8.0.11 + '@types/koa-bodyparser': 4.3.7 '@types/lodash': 4.14.184 '@types/luxon': 3.0.0 '@types/react': 18.0.17 @@ -305,6 +307,21 @@ importers: eslint: 8.22.0 typescript: 4.7.4 + packages/open-payments: + specifiers: + '@types/node': ^18.7.12 + axios: ^1.1.2 + openapi-typescript: ^4.5.0 + ts-node: ^10.7.0 + typescript: ^4.3.0 + dependencies: + axios: 1.1.2 + devDependencies: + '@types/node': 18.7.13 + openapi-typescript: 4.5.0 + ts-node: 10.9.1_ieummqxttktzud32hpyrer46t4 + typescript: 4.8.4 + packages/openapi: specifiers: '@apidevtools/json-schema-ref-parser': ^9.0.9 @@ -325,7 +342,7 @@ importers: dependencies: '@apidevtools/json-schema-ref-parser': 9.0.9 ajv: 8.11.0 - ajv-formats: 2.1.1_ajv@8.11.0 + ajv-formats: 2.1.1 openapi-default-setter: 12.0.0 openapi-request-coercer: 12.0.0 openapi-request-validator: 12.0.0 @@ -533,6 +550,7 @@ packages: /@babel/code-frame/7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} + requiresBuild: true dependencies: '@babel/highlight': 7.18.6 dev: true @@ -2169,7 +2187,7 @@ packages: '@types/node': 18.7.6 chalk: 4.1.2 cosmiconfig: 7.0.1 - cosmiconfig-typescript-loader: 2.0.2_nhjjlp4mdozido4npc63jtte4a + cosmiconfig-typescript-loader: 2.0.2_hxbm2pyqntnffeffb7yzpexbtu lodash: 4.17.21 resolve-from: 5.0.0 typescript: 4.7.4 @@ -4496,10 +4514,8 @@ packages: indent-string: 4.0.0 dev: true - /ajv-formats/2.1.1_ajv@8.11.0: + /ajv-formats/2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true @@ -4658,9 +4674,9 @@ packages: '@koa/cors': 3.3.0 '@types/accepts': 1.3.5 '@types/koa': 2.13.5 + '@types/koa__cors': 3.3.0 '@types/koa-bodyparser': 4.3.7 '@types/koa-compose': 3.2.5 - '@types/koa__cors': 3.3.0 accepts: 1.3.8 apollo-server-core: 3.10.1_graphql@16.6.0 apollo-server-types: 3.6.2_graphql@16.6.0 @@ -4958,6 +4974,16 @@ packages: - debug dev: false + /axios/1.1.2: + resolution: {integrity: sha512-bznQyETwElsXl2RK7HLLwb5GPpOLlycxHCtrpDR/4RqqBzjARaOTo3jz4IgtntWUYee7Ne4S8UHd92VCuzPaWA==} + dependencies: + follow-redirects: 1.15.1 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query/2.2.0: resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==} dev: true @@ -5812,8 +5838,8 @@ packages: engines: {node: '>=10'} hasBin: true dependencies: - JSONStream: 1.3.5 is-text-path: 1.0.1 + JSONStream: 1.3.5 lodash: 4.17.21 meow: 8.1.2 split2: 3.2.2 @@ -5886,12 +5912,11 @@ packages: '@iarna/toml': 2.2.5 dev: true - /cosmiconfig-typescript-loader/2.0.2_nhjjlp4mdozido4npc63jtte4a: + /cosmiconfig-typescript-loader/2.0.2_hxbm2pyqntnffeffb7yzpexbtu: resolution: {integrity: sha512-KmE+bMjWMXJbkWCeY4FJX/npHuZPNr9XF9q9CIQ/bpFwi1qHfCmSiKarrCcRa0LO4fWjk93pVoeRtJAkTGcYNw==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@types/node': '*' - cosmiconfig: '>=7' typescript: '>=3' dependencies: '@types/node': 18.7.6 @@ -7662,6 +7687,10 @@ packages: type-fest: 0.20.2 dev: true + /globalyzer/0.1.0: + resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + dev: true + /globby/10.0.0: resolution: {integrity: sha512-3LifW9M4joGZasyYPz2A1U74zbC/45fvpXUvO/9KbSa+VV0aGZarWkfdgKyR9sExNP0t0x0ss/UMJpNpcaTspw==} engines: {node: '>=8'} @@ -7687,6 +7716,10 @@ packages: merge2: 1.4.1 slash: 3.0.0 + /globrex/0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true + /got/11.8.5: resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==} engines: {node: '>=10.19.0'} @@ -7945,6 +7978,13 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true + /hosted-git-info/3.0.8: + resolution: {integrity: sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true + /hosted-git-info/4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} @@ -9897,6 +9937,24 @@ packages: yargs-parser: 20.2.9 dev: true + /meow/9.0.0: + resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize: 1.2.0 + decamelize-keys: 1.1.0 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + dev: true + /merge-descriptors/1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} @@ -10234,6 +10292,12 @@ packages: engines: {node: '>=4'} hasBin: true + /mime/3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: true + /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -10721,7 +10785,7 @@ packages: resolution: {integrity: sha512-z0K3fRViKbamg80RJgNLzLZik2QBvXVfHgtpK4nGlTQ7qgn06PApFzF1Arc4Fb3aAB26BFBQGzjrojT58XjA3A==} dependencies: ajv: 8.11.0 - ajv-formats: 2.1.1_ajv@8.11.0 + ajv-formats: 2.1.1 content-type: 1.0.4 openapi-jsonschema-parameters: 12.0.0 openapi-types: 12.0.0 @@ -10746,7 +10810,7 @@ packages: resolution: {integrity: sha512-5wpFKMoEbUcjiqo16jIen3Cb2+oApSnYZpWn8WQdRO2q/dNQZZl8Pz6ESwCriiyU5AK4i5ZI6+7O3bHQr6+6+g==} dependencies: ajv: 8.11.0 - ajv-formats: 2.1.1_ajv@8.11.0 + ajv-formats: 2.1.1 lodash.merge: 4.6.2 openapi-types: 9.3.1 dev: true @@ -10758,6 +10822,24 @@ packages: resolution: {integrity: sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==} dev: true + /openapi-typescript/4.5.0: + resolution: {integrity: sha512-++gWZLTKmbZP608JHMerllAs84HzULWfVjfH7otkWBLrKxUvzHMFqI6R4JSW1LoNDZnS4KKiRTZW66Fxyp6z4Q==} + engines: {node: '>= 12.0.0', npm: '>= 7.0.0'} + hasBin: true + dependencies: + hosted-git-info: 3.0.8 + js-yaml: 4.1.0 + kleur: 4.1.5 + meow: 9.0.0 + mime: 3.0.0 + node-fetch: 2.6.7 + prettier: 2.7.1 + slash: 3.0.0 + tiny-glob: 0.2.9 + transitivePeerDependencies: + - encoding + dev: true + /openapi-validator/0.14.2: resolution: {integrity: sha512-bgRQLZoxmECTjRxfpyMorad1ll58biUdV+31ALsHW2gRzdtMscI4Qm/wuhG8HsDUMGQkVLQYzUgJijNGKD65Og==} dependencies: @@ -11285,6 +11367,10 @@ packages: forwarded: 0.2.0 ipaddr.js: 1.9.1 + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /pump/2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} dependencies: @@ -12516,6 +12602,13 @@ packages: engines: {node: '>=8'} dev: false + /tiny-glob/0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + dev: true + /title-case/3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} dependencies: @@ -12737,6 +12830,37 @@ packages: yn: 3.1.1 dev: true + /ts-node/10.9.1_ieummqxttktzud32hpyrer46t4: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 18.7.13 + acorn: 8.8.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.8.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /ts-node/9.1.1_typescript@4.7.4: resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==} engines: {node: '>=10.0.0'} @@ -12863,6 +12987,12 @@ packages: hasBin: true dev: true + /typescript/4.8.4: + resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + /ua-parser-js/0.7.31: resolution: {integrity: sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==} dev: true From e067011f996dc2ce7f15fc9b9aedbffe55172e12 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 14:51:01 +0200 Subject: [PATCH 09/33] feat(open-payments): update pnpm-lock.yaml --- pnpm-lock.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cff2e1ee27..dd45873785 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,7 +170,6 @@ importers: objection: ^3.0.1 objection-db-errors: ^1.1.2 oer-utils: 5.1.3-alpha.1 - open-payments: workspace:../open-payments openapi: workspace:../openapi openapi-types: ^12.0.0 pg: ^8.6.0 @@ -223,7 +222,6 @@ importers: objection: 3.0.1_knex@0.95.15 objection-db-errors: 1.1.2_objection@3.0.1 oer-utils: 5.1.3-alpha.1 - open-payments: link:../open-payments openapi: link:../openapi pg: 8.7.3 pino: 8.4.2 From dc288785bfbe9278584fe7cb36154c0b2fdca565 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 15:28:51 +0200 Subject: [PATCH 10/33] feat(open-payments): adding tests --- packages/open-payments/jest.config.js | 17 ++++++ packages/open-payments/package.json | 4 +- packages/open-payments/src/client.test.ts | 71 +++++++++++++++++++++++ packages/open-payments/src/client.ts | 21 +++++-- 4 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 packages/open-payments/jest.config.js create mode 100644 packages/open-payments/src/client.test.ts diff --git a/packages/open-payments/jest.config.js b/packages/open-payments/jest.config.js new file mode 100644 index 0000000000..a2667d704f --- /dev/null +++ b/packages/open-payments/jest.config.js @@ -0,0 +1,17 @@ +'use strict' +// eslint-disable-next-line @typescript-eslint/no-var-requires +const baseConfig = require('../../jest.config.base.js') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const packageName = require('./package.json').name + +module.exports = { + ...baseConfig, + clearMocks: true, + roots: [`/packages/${packageName}`], + testRegex: `(packages/${packageName}/.*/__tests__/.*|\\.(test|spec))\\.tsx?$`, + moduleDirectories: [`node_modules`, `packages/${packageName}/node_modules`], + modulePaths: [`/packages/${packageName}/src/`], + id: packageName, + displayName: packageName, + rootDir: '../..' +} diff --git a/packages/open-payments/package.json b/packages/open-payments/package.json index ea1c7a6dae..6e6f697c38 100644 --- a/packages/open-payments/package.json +++ b/packages/open-payments/package.json @@ -9,11 +9,13 @@ "scripts": { "build": "pnpm clean && tsc --build tsconfig.json", "clean": "rm -fr dist/", + "generate:types": "npx ts-node scripts/generate-types.ts", "prepack": "pnpm build", - "generate:types": "npx ts-node scripts/generate-types.ts" + "test": "jest --passWithNoTests" }, "devDependencies": { "@types/node": "^18.7.12", + "nock": "^13.2.9", "openapi-typescript": "^4.5.0", "ts-node": "^10.7.0", "typescript": "^4.3.0" diff --git a/packages/open-payments/src/client.test.ts b/packages/open-payments/src/client.test.ts new file mode 100644 index 0000000000..e270fbb5ea --- /dev/null +++ b/packages/open-payments/src/client.test.ts @@ -0,0 +1,71 @@ +import { createAxiosInstance, get } from './client' +import nock from 'nock' + +describe('open-payments', (): void => { + describe('createAxiosInstance', (): void => { + test('sets timeout properly', async (): Promise => { + expect(createAxiosInstance({ timeout: 1000 }).defaults.timeout).toBe(1000) + }) + test('sets Content-Type header properly', async (): Promise => { + expect( + createAxiosInstance().defaults.headers.common['Content-Type'] + ).toBe('application/json') + }) + }) + + describe('get', (): void => { + const axiosInstance = createAxiosInstance() + const baseUrl = 'http://localhost:1000' + + beforeEach(() => { + jest.spyOn(axiosInstance, 'get') + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test('sets headers properly if accessToken provided', async (): Promise => { + nock(baseUrl) + .get('/incoming-payment') + .reply(200, () => ({ + validReceiver: 0 + })) + + await get(axiosInstance, { + url: `${baseUrl}/incoming-payment`, + accessToken: 'accessToken' + }) + + expect(axiosInstance.get).toHaveBeenCalledWith( + `${baseUrl}/incoming-payment`, + { + headers: { + Authorization: 'GNAP accessToken', + Signature: 'TODO', + 'Signature-Input': 'TODO' + } + } + ) + }) + + test('sets headers properly if accessToken is not provided', async (): Promise => { + nock(baseUrl) + .get('/incoming-payment') + .reply(200, () => ({ + validReceiver: 0 + })) + + await get(axiosInstance, { + url: `${baseUrl}/incoming-payment` + }) + + expect(axiosInstance.get).toHaveBeenCalledWith( + `${baseUrl}/incoming-payment`, + { + headers: {} + } + ) + }) + }) +}) diff --git a/packages/open-payments/src/client.ts b/packages/open-payments/src/client.ts index 0e882b8bf5..b6ff930712 100644 --- a/packages/open-payments/src/client.ts +++ b/packages/open-payments/src/client.ts @@ -8,7 +8,7 @@ interface CreateOpenPaymentClientArgs { interface GetArgs { url: string - accessToken: string + accessToken?: string } export interface OpenPaymentsClient { @@ -20,7 +20,10 @@ export interface OpenPaymentsClient { } } -const get = async (axios: AxiosInstance, args: GetArgs): Promise => { +export const get = async ( + axios: AxiosInstance, + args: GetArgs +): Promise => { const { url, accessToken } = args const { data } = await axios.get(url, { @@ -36,15 +39,23 @@ const get = async (axios: AxiosInstance, args: GetArgs): Promise => { return data } -export const createClient = ( +export const createAxiosInstance = ( args?: CreateOpenPaymentClientArgs -): OpenPaymentsClient => { +): AxiosInstance => { const axiosInstance = axios.create({ - timeout: args.timeout ?? config.DEFAULT_REQUEST_TIMEOUT + timeout: args?.timeout ?? config.DEFAULT_REQUEST_TIMEOUT }) axiosInstance.defaults.headers.common['Content-Type'] = 'application/json' + return axiosInstance +} + +export const createClient = ( + args?: CreateOpenPaymentClientArgs +): OpenPaymentsClient => { + const axios = createAxiosInstance(args) + return { incomingPayment: { get: (args: GetArgs) => get(axios, args) From 39b561b12c651bcc7585589b16ed29660ecb573a Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 15:30:03 +0200 Subject: [PATCH 11/33] feat(open-payments): update pnpm-lock.yaml --- pnpm-lock.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd45873785..e663d96185 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -309,6 +309,7 @@ importers: specifiers: '@types/node': ^18.7.12 axios: ^1.1.2 + nock: ^13.2.9 openapi-typescript: ^4.5.0 ts-node: ^10.7.0 typescript: ^4.3.0 @@ -316,6 +317,7 @@ importers: axios: 1.1.2 devDependencies: '@types/node': 18.7.13 + nock: 13.2.9 openapi-typescript: 4.5.0 ts-node: 10.9.1_ieummqxttktzud32hpyrer46t4 typescript: 4.8.4 From 9393e47393b9b7de15812f57897b7ba9e4f6ad79 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 15:35:36 +0200 Subject: [PATCH 12/33] feat(open-payments): simplify test --- packages/open-payments/src/client.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/open-payments/src/client.test.ts b/packages/open-payments/src/client.test.ts index e270fbb5ea..da502deaea 100644 --- a/packages/open-payments/src/client.test.ts +++ b/packages/open-payments/src/client.test.ts @@ -21,10 +21,6 @@ describe('open-payments', (): void => { jest.spyOn(axiosInstance, 'get') }) - afterEach(() => { - jest.clearAllMocks() - }) - test('sets headers properly if accessToken provided', async (): Promise => { nock(baseUrl) .get('/incoming-payment') From 3c3a0b76d997cf3ac020f3f36418c4a71ca20ef1 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 15:42:14 +0200 Subject: [PATCH 13/33] feat(open-payments): updating workflows --- .github/labeler.yml | 2 ++ .github/workflows/lint_test_build.yml | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index 67fd85dc75..5b9ea5507c 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -5,6 +5,7 @@ 'pkg: auth': packages/auth/**/* 'pkg: openapi': packages/openapi/**/* 'pkg: map': packages/mock-account-provider/**/* +'pkg: open-payments': packages/open-payments/**/* # Add 'type: documentation' label to any change to *.md files 'type: ci': @@ -22,6 +23,7 @@ - packages/frontend/src/**/* - packages/auth/src/**/* - packages/openapi/src/**/* + - packages/open-payments/src/**/* # Add 'type: tests' label to any change to *.test.ts files 'type: tests': packages/**/*.test.ts diff --git a/.github/workflows/lint_test_build.yml b/.github/workflows/lint_test_build.yml index 221296abcb..9a55454eec 100644 --- a/.github/workflows/lint_test_build.yml +++ b/.github/workflows/lint_test_build.yml @@ -57,6 +57,15 @@ jobs: - uses: ./.github/workflows/rafiki/env-setup - run: pnpm --filter openapi test + open-payments: + runs-on: ubuntu-latest + needs: checkout + timeout-minutes: 5 + steps: + - uses: actions/checkout@v2 + - uses: ./.github/workflows/rafiki/env-setup + - run: pnpm --filter open-payments test + build: runs-on: ubuntu-latest timeout-minutes: 5 @@ -65,6 +74,7 @@ jobs: - frontend - auth - openapi + - open-payments steps: - uses: actions/checkout@v2 - uses: ./.github/workflows/rafiki/env-setup From 120dcae03cc70ce8a065a602cec55b7b527b309a Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Fri, 14 Oct 2022 17:50:20 +0200 Subject: [PATCH 14/33] feat(open-payments): pin the open api spec to the most recent commit --- packages/open-payments/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/open-payments/src/config.ts b/packages/open-payments/src/config.ts index f277636b12..ed62374b63 100644 --- a/packages/open-payments/src/config.ts +++ b/packages/open-payments/src/config.ts @@ -1,5 +1,5 @@ export default { OPEN_PAYMENTS_OPEN_API_URL: - 'https://raw.githubusercontent.com/interledger/open-payments/main/open-api-spec.yaml', + 'https://raw.githubusercontent.com/interledger/open-payments/4c873dba89164decffbe84905d12f1d4ec045389/open-api-spec.yaml', DEFAULT_REQUEST_TIMEOUT: 3_000 } From 80d985617d72843412d0de9065fda6701ffd8826 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Tue, 18 Oct 2022 14:29:08 +0200 Subject: [PATCH 15/33] feat(open-payments): adding openapi validation --- packages/open-payments/src/client.test.ts | 67 ------------------- packages/open-payments/src/client.ts | 67 ------------------- .../src/client/ilp-stream-connection.ts | 19 ++++++ .../src/client/incoming-payment.ts | 19 ++++++ packages/open-payments/src/client/index.ts | 36 ++++++++++ packages/open-payments/src/client/requests.ts | 60 +++++++++++++++++ packages/open-payments/src/index.ts | 2 +- packages/open-payments/src/types.ts | 5 +- 8 files changed, 139 insertions(+), 136 deletions(-) delete mode 100644 packages/open-payments/src/client.test.ts delete mode 100644 packages/open-payments/src/client.ts create mode 100644 packages/open-payments/src/client/ilp-stream-connection.ts create mode 100644 packages/open-payments/src/client/incoming-payment.ts create mode 100644 packages/open-payments/src/client/index.ts create mode 100644 packages/open-payments/src/client/requests.ts diff --git a/packages/open-payments/src/client.test.ts b/packages/open-payments/src/client.test.ts deleted file mode 100644 index da502deaea..0000000000 --- a/packages/open-payments/src/client.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { createAxiosInstance, get } from './client' -import nock from 'nock' - -describe('open-payments', (): void => { - describe('createAxiosInstance', (): void => { - test('sets timeout properly', async (): Promise => { - expect(createAxiosInstance({ timeout: 1000 }).defaults.timeout).toBe(1000) - }) - test('sets Content-Type header properly', async (): Promise => { - expect( - createAxiosInstance().defaults.headers.common['Content-Type'] - ).toBe('application/json') - }) - }) - - describe('get', (): void => { - const axiosInstance = createAxiosInstance() - const baseUrl = 'http://localhost:1000' - - beforeEach(() => { - jest.spyOn(axiosInstance, 'get') - }) - - test('sets headers properly if accessToken provided', async (): Promise => { - nock(baseUrl) - .get('/incoming-payment') - .reply(200, () => ({ - validReceiver: 0 - })) - - await get(axiosInstance, { - url: `${baseUrl}/incoming-payment`, - accessToken: 'accessToken' - }) - - expect(axiosInstance.get).toHaveBeenCalledWith( - `${baseUrl}/incoming-payment`, - { - headers: { - Authorization: 'GNAP accessToken', - Signature: 'TODO', - 'Signature-Input': 'TODO' - } - } - ) - }) - - test('sets headers properly if accessToken is not provided', async (): Promise => { - nock(baseUrl) - .get('/incoming-payment') - .reply(200, () => ({ - validReceiver: 0 - })) - - await get(axiosInstance, { - url: `${baseUrl}/incoming-payment` - }) - - expect(axiosInstance.get).toHaveBeenCalledWith( - `${baseUrl}/incoming-payment`, - { - headers: {} - } - ) - }) - }) -}) diff --git a/packages/open-payments/src/client.ts b/packages/open-payments/src/client.ts deleted file mode 100644 index b6ff930712..0000000000 --- a/packages/open-payments/src/client.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ILPStreamConnection, IncomingPayment } from './types' -import axios, { AxiosInstance } from 'axios' -import config from './config' - -interface CreateOpenPaymentClientArgs { - timeout?: number -} - -interface GetArgs { - url: string - accessToken?: string -} - -export interface OpenPaymentsClient { - incomingPayment: { - get(args: GetArgs): Promise - } - ilpStreamConnection: { - get(args: GetArgs): Promise - } -} - -export const get = async ( - axios: AxiosInstance, - args: GetArgs -): Promise => { - const { url, accessToken } = args - - const { data } = await axios.get(url, { - headers: accessToken - ? { - Authorization: `GNAP ${accessToken}`, - Signature: 'TODO', - 'Signature-Input': 'TODO' - } - : {} - }) - - return data -} - -export const createAxiosInstance = ( - args?: CreateOpenPaymentClientArgs -): AxiosInstance => { - const axiosInstance = axios.create({ - timeout: args?.timeout ?? config.DEFAULT_REQUEST_TIMEOUT - }) - - axiosInstance.defaults.headers.common['Content-Type'] = 'application/json' - - return axiosInstance -} - -export const createClient = ( - args?: CreateOpenPaymentClientArgs -): OpenPaymentsClient => { - const axios = createAxiosInstance(args) - - return { - incomingPayment: { - get: (args: GetArgs) => get(axios, args) - }, - ilpStreamConnection: { - get: (args: GetArgs) => get(axios, args) - } - } -} diff --git a/packages/open-payments/src/client/ilp-stream-connection.ts b/packages/open-payments/src/client/ilp-stream-connection.ts new file mode 100644 index 0000000000..fe8db4b767 --- /dev/null +++ b/packages/open-payments/src/client/ilp-stream-connection.ts @@ -0,0 +1,19 @@ +import { AxiosInstance } from 'axios' +import { OpenAPI, HttpMethod } from 'openapi' +import { getPath, ILPStreamConnection } from '../types' +import { GetArgs, get } from './requests' + +export const getILPStreamConnection = async ( + axios: AxiosInstance, + openApi: OpenAPI, + args: GetArgs +): Promise => { + return get( + axios, + args, + openApi.createResponseValidator({ + path: getPath('/connections/{id}'), + method: HttpMethod.GET + }) + ) +} diff --git a/packages/open-payments/src/client/incoming-payment.ts b/packages/open-payments/src/client/incoming-payment.ts new file mode 100644 index 0000000000..6303ad8fa8 --- /dev/null +++ b/packages/open-payments/src/client/incoming-payment.ts @@ -0,0 +1,19 @@ +import { AxiosInstance } from 'axios' +import { OpenAPI, HttpMethod } from 'openapi' +import { IncomingPayment, getPath } from '../types' +import { GetArgs, get } from './requests' + +export const getIncomingPayment = async ( + axios: AxiosInstance, + openApi: OpenAPI, + args: GetArgs +): Promise => { + return get( + axios, + args, + openApi.createResponseValidator({ + path: getPath('/incoming-payments/{id}'), + method: HttpMethod.GET + }) + ) +} diff --git a/packages/open-payments/src/client/index.ts b/packages/open-payments/src/client/index.ts new file mode 100644 index 0000000000..ad47e9aa3f --- /dev/null +++ b/packages/open-payments/src/client/index.ts @@ -0,0 +1,36 @@ +import { createOpenAPI } from 'openapi' + +import { ILPStreamConnection, IncomingPayment } from '../types' +import config from '../config' +import { getIncomingPayment } from './incoming-payment' +import { getILPStreamConnection } from './ilp-stream-connection' +import { createAxiosInstance, GetArgs } from './requests' + +export interface CreateOpenPaymentClientArgs { + timeout?: number +} + +export interface OpenPaymentsClient { + incomingPayment: { + get(args: GetArgs): Promise + } + ilpStreamConnection: { + get(args: GetArgs): Promise + } +} + +export const createClient = async ( + args?: CreateOpenPaymentClientArgs +): Promise => { + const axios = createAxiosInstance(args) + const openApi = await createOpenAPI(config.OPEN_PAYMENTS_OPEN_API_URL) + + return { + incomingPayment: { + get: (args: GetArgs) => getIncomingPayment(axios, openApi, args) + }, + ilpStreamConnection: { + get: (args: GetArgs) => getILPStreamConnection(axios, openApi, args) + } + } +} diff --git a/packages/open-payments/src/client/requests.ts b/packages/open-payments/src/client/requests.ts new file mode 100644 index 0000000000..909214dbc7 --- /dev/null +++ b/packages/open-payments/src/client/requests.ts @@ -0,0 +1,60 @@ +import axios, { AxiosInstance } from 'axios' +import { ValidateFunction } from 'openapi' +import { CreateOpenPaymentClientArgs } from '.' +import config from '../config' + +export interface GetArgs { + url: string + accessToken?: string +} + +export const get = async ( + axios: AxiosInstance, + args: GetArgs, + responseValidator: ValidateFunction +): Promise => { + const { url, accessToken } = args + + try { + const { data } = await axios.get(url, { + headers: accessToken + ? { + Authorization: `GNAP ${accessToken}`, + Signature: 'TODO', + 'Signature-Input': 'TODO' + } + : {} + }) + + if (!responseValidator(data)) { + const errorMessage = 'Failed to validate OpenApi response' + console.log(errorMessage, { + url, + data: JSON.stringify(data) + }) + + throw new Error(errorMessage) + } + + return data + } catch (error) { + console.log('Error when making Open Payments GET request', { + errorMessage: error?.message, + url + }) + + throw error + } +} + +export const createAxiosInstance = ( + args?: CreateOpenPaymentClientArgs +): AxiosInstance => { + const axiosInstance = axios.create({ + timeout: args?.timeout ?? config.DEFAULT_REQUEST_TIMEOUT + }) + + axiosInstance.defaults.headers.common['Content-Type'] = 'application/json' + + return axiosInstance +} diff --git a/packages/open-payments/src/index.ts b/packages/open-payments/src/index.ts index 8e8c16c9bf..0cc9edc2a2 100644 --- a/packages/open-payments/src/index.ts +++ b/packages/open-payments/src/index.ts @@ -1,2 +1,2 @@ -export * from './types' +export { IncomingPayment, ILPStreamConnection } from './types' export { createClient, OpenPaymentsClient } from './client' diff --git a/packages/open-payments/src/types.ts b/packages/open-payments/src/types.ts index a282d9b169..82dd47c3c9 100644 --- a/packages/open-payments/src/types.ts +++ b/packages/open-payments/src/types.ts @@ -1,4 +1,7 @@ -import { components } from './generated/types' +import { components, paths as Paths } from './generated/types' + +export const getPath =

(path: P): string => + path as string export type IncomingPayment = components['schemas']['incoming-payment'] export type ILPStreamConnection = components['schemas']['ilp-stream-connection'] From 218f1f0119237b03bc5cb65c437f40f516b4e219 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Tue, 18 Oct 2022 14:29:20 +0200 Subject: [PATCH 16/33] feat(open-payments): adding openapi validation --- packages/open-payments/package.json | 3 ++- pnpm-lock.yaml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/open-payments/package.json b/packages/open-payments/package.json index 6e6f697c38..2caaef6075 100644 --- a/packages/open-payments/package.json +++ b/packages/open-payments/package.json @@ -21,6 +21,7 @@ "typescript": "^4.3.0" }, "dependencies": { - "axios": "^1.1.2" + "axios": "^1.1.2", + "openapi": "workspace:../openapi" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e663d96185..68a7a7d685 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -310,11 +310,13 @@ importers: '@types/node': ^18.7.12 axios: ^1.1.2 nock: ^13.2.9 + openapi: workspace:../openapi openapi-typescript: ^4.5.0 ts-node: ^10.7.0 typescript: ^4.3.0 dependencies: axios: 1.1.2 + openapi: link:../openapi devDependencies: '@types/node': 18.7.13 nock: 13.2.9 From d7c203c5d910a573421039634b3e92733c01dd11 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Tue, 18 Oct 2022 14:29:37 +0200 Subject: [PATCH 17/33] feat(open-payments): adding tests --- .../src/client/ilp-stream-connection.test.ts | 37 ++++++++ .../src/client/incoming-payment.test.ts | 37 ++++++++ .../open-payments/src/client/requests.test.ts | 86 +++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 packages/open-payments/src/client/ilp-stream-connection.test.ts create mode 100644 packages/open-payments/src/client/incoming-payment.test.ts create mode 100644 packages/open-payments/src/client/requests.test.ts diff --git a/packages/open-payments/src/client/ilp-stream-connection.test.ts b/packages/open-payments/src/client/ilp-stream-connection.test.ts new file mode 100644 index 0000000000..957cb4b694 --- /dev/null +++ b/packages/open-payments/src/client/ilp-stream-connection.test.ts @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { getILPStreamConnection } from './ilp-stream-connection' +import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' +import { createAxiosInstance } from './requests' +import config from '../config' + +jest.mock('./requests', () => ({ + ...jest.requireActual('./requests'), + get: jest.fn() +})) + +describe('ilp-stream-connection', (): void => { + let openApi: OpenAPI + + beforeAll(async () => { + openApi = await createOpenAPI(config.OPEN_PAYMENTS_OPEN_API_URL) + }) + + const axiosInstance = createAxiosInstance() + + describe('getIncomingPayment', (): void => { + test('calls createResponseValidator properly', async (): Promise => { + const createResponseValidatorSpy = jest.spyOn( + openApi, + 'createResponseValidator' + ) + + await getILPStreamConnection(axiosInstance, openApi, { + url: 'http://localhost:1000/incoming-payment' + }) + expect(createResponseValidatorSpy).toHaveBeenCalledWith({ + path: '/connections/{id}', + method: HttpMethod.GET + }) + }) + }) +}) diff --git a/packages/open-payments/src/client/incoming-payment.test.ts b/packages/open-payments/src/client/incoming-payment.test.ts new file mode 100644 index 0000000000..ac29c328ef --- /dev/null +++ b/packages/open-payments/src/client/incoming-payment.test.ts @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { getIncomingPayment } from './incoming-payment' +import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' +import { createAxiosInstance } from './requests' +import config from '../config' + +jest.mock('./requests', () => ({ + ...jest.requireActual('./requests'), + get: jest.fn() +})) + +describe('incoming-payment', (): void => { + let openApi: OpenAPI + + beforeAll(async () => { + openApi = await createOpenAPI(config.OPEN_PAYMENTS_OPEN_API_URL) + }) + + const axiosInstance = createAxiosInstance() + + describe('getIncomingPayment', (): void => { + test('calls createResponseValidator properly', async (): Promise => { + const createResponseValidatorSpy = jest.spyOn( + openApi, + 'createResponseValidator' + ) + + await getIncomingPayment(axiosInstance, openApi, { + url: 'http://localhost:1000/incoming-payment' + }) + expect(createResponseValidatorSpy).toHaveBeenCalledWith({ + path: '/incoming-payments/{id}', + method: HttpMethod.GET + }) + }) + }) +}) diff --git a/packages/open-payments/src/client/requests.test.ts b/packages/open-payments/src/client/requests.test.ts new file mode 100644 index 0000000000..08f911f336 --- /dev/null +++ b/packages/open-payments/src/client/requests.test.ts @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { createAxiosInstance, get } from './requests' +import nock from 'nock' + +describe('requests', (): void => { + jest.spyOn(console, 'log').mockImplementation(() => {}) + + describe('createAxiosInstance', (): void => { + test('sets timeout properly', async (): Promise => { + expect(createAxiosInstance({ timeout: 1000 }).defaults.timeout).toBe(1000) + }) + test('sets Content-Type header properly', async (): Promise => { + expect( + createAxiosInstance().defaults.headers.common['Content-Type'] + ).toBe('application/json') + }) + }) + + describe('get', (): void => { + const axiosInstance = createAxiosInstance() + const baseUrl = 'http://localhost:1000' + const successfulValidator = (data: unknown): data is unknown => true + const failedValidator = (data: unknown): data is unknown => false + + beforeAll(() => { + jest.spyOn(axiosInstance, 'get') + }) + + test('sets headers properly if accessToken provided', async (): Promise => { + nock(baseUrl).get('/incoming-payment').reply(200) + + await get( + axiosInstance, + { + url: `${baseUrl}/incoming-payment`, + accessToken: 'accessToken' + }, + successfulValidator + ) + + expect(axiosInstance.get).toHaveBeenCalledWith( + `${baseUrl}/incoming-payment`, + { + headers: { + Authorization: 'GNAP accessToken', + Signature: 'TODO', + 'Signature-Input': 'TODO' + } + } + ) + }) + + test('sets headers properly if accessToken is not provided', async (): Promise => { + nock(baseUrl).get('/incoming-payment').reply(200) + + await get( + axiosInstance, + { + url: `${baseUrl}/incoming-payment` + }, + successfulValidator + ) + + expect(axiosInstance.get).toHaveBeenCalledWith( + `${baseUrl}/incoming-payment`, + { + headers: {} + } + ) + }) + + test('throws if response validator function fails', async (): Promise => { + nock(baseUrl).get('/incoming-payment').reply(200) + + await expect( + get( + axiosInstance, + { + url: `${baseUrl}/incoming-payment` + }, + failedValidator + ) + ).rejects.toThrow('Failed to validate OpenApi response') + }) + }) +}) From dfc07cb7a9e76753e7efa25b5744542c917a0077 Mon Sep 17 00:00:00 2001 From: "maxkurapov@gmail.com" Date: Tue, 18 Oct 2022 14:33:31 +0200 Subject: [PATCH 18/33] feat(open-payments): correcting test --- packages/open-payments/src/client/ilp-stream-connection.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/open-payments/src/client/ilp-stream-connection.test.ts b/packages/open-payments/src/client/ilp-stream-connection.test.ts index 957cb4b694..f347ad6af1 100644 --- a/packages/open-payments/src/client/ilp-stream-connection.test.ts +++ b/packages/open-payments/src/client/ilp-stream-connection.test.ts @@ -18,7 +18,7 @@ describe('ilp-stream-connection', (): void => { const axiosInstance = createAxiosInstance() - describe('getIncomingPayment', (): void => { + describe('getILPStreamConnection', (): void => { test('calls createResponseValidator properly', async (): Promise => { const createResponseValidatorSpy = jest.spyOn( openApi, From 43335f69b89ca46d59d3e177ac1add2daa9a060c Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Fri, 21 Oct 2022 20:07:07 +0200 Subject: [PATCH 19/33] feat(open-payments): use updated RS spec --- packages/open-payments/src/config.ts | 2 +- packages/open-payments/src/generated/types.ts | 247 +++++++++++++----- packages/open-payments/src/types.ts | 3 +- 3 files changed, 189 insertions(+), 63 deletions(-) diff --git a/packages/open-payments/src/config.ts b/packages/open-payments/src/config.ts index ed62374b63..7c16ca7088 100644 --- a/packages/open-payments/src/config.ts +++ b/packages/open-payments/src/config.ts @@ -1,5 +1,5 @@ export default { OPEN_PAYMENTS_OPEN_API_URL: - 'https://raw.githubusercontent.com/interledger/open-payments/4c873dba89164decffbe84905d12f1d4ec045389/open-api-spec.yaml', + 'https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/RS/openapi.yaml', DEFAULT_REQUEST_TIMEOUT: 3_000 } diff --git a/packages/open-payments/src/generated/types.ts b/packages/open-payments/src/generated/types.ts index 95973a957a..d3398c3d86 100644 --- a/packages/open-payments/src/generated/types.ts +++ b/packages/open-payments/src/generated/types.ts @@ -14,6 +14,10 @@ export interface paths { */ get: operations["get-payment-pointer"]; }; + "/jwks.json": { + /** Retrieve the public keys of the Payment Pointer. */ + get: operations["get-payment-pointer-keys"]; + }; "/connections/{id}": { /** * *NB* Use server url specific to this path. @@ -114,15 +118,21 @@ export interface components { id: string; /** @description A public name for the account. This should be set by the account holder with their provider to provide a hint to counterparties as to the identity of the account holder. */ publicName?: string; - /** @description The asset code of the account. */ - assetCode: components["schemas"]["assetCode"]; - assetScale: components["schemas"]["assetScale"]; + assetCode: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["assetCode"]; + assetScale: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["assetScale"]; /** * Format: uri * @description The URL of the authorization server endpoint for getting grants and access tokens for this payment pointer. */ authServer: string; }; + /** + * JSON Web Key Set document + * @description A JSON Web Key Set document according to [rfc7517](https://datatracker.ietf.org/doc/html/rfc7517) listing the keys associated with this payment pointer. These keys are used to sign requests made by this payment pointer. + */ + "json-web-key-set": { + keys: components["schemas"]["json-web-key"][]; + }; /** * ILP Stream Connection * @description An **ILP STREAM Connection** is an endpoint that returns unique STREAM connection credentials to establish a STREAM connection to the underlying account. @@ -137,10 +147,8 @@ export interface components { ilpAddress: string; /** @description The base64 url-encoded shared secret to use when establishing a STREAM connection. */ sharedSecret: string; - /** @description The asset code of the amount. */ - assetCode: components["schemas"]["assetCode"]; - /** @description The scale of the amount. */ - assetScale: components["schemas"]["assetScale"]; + assetCode: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["assetCode"]; + assetScale: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["assetScale"]; }; /** * Incoming Payment @@ -160,9 +168,9 @@ export interface components { /** @description Describes whether the incoming payment has completed receiving fund. */ completed: boolean; /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ - incomingAmount?: components["schemas"]["amount"]; + incomingAmount?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; /** @description The total amount that has been paid into the payment pointer under this incoming payment. */ - receivedAmount: components["schemas"]["amount"]; + receivedAmount: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; /** * Format: date-time * @description The date and time when payments under this incoming payment will no longer be accepted. @@ -224,13 +232,13 @@ export interface components { /** @description Describes whether the payment failed to send its full amount. */ failed?: boolean; /** @description The URL of the incoming payment or ILP STREAM Connection that is being paid. */ - receiver: components["schemas"]["receiver"]; + receiver: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["receiver"]; /** @description The total amount that should be received by the receiver when this outgoing payment has been paid. */ - receiveAmount: components["schemas"]["amount"]; + receiveAmount: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; /** @description The total amount that should be sent when this outgoing payment has been paid. */ - sendAmount: components["schemas"]["amount"]; + sendAmount: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; /** @description The total amount that has been sent under this outgoing payment. */ - sentAmount: components["schemas"]["amount"]; + sentAmount: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; /** @description Human readable description of the outgoing payment that will be visible to the account holder and shared with the receiver. */ description?: string; /** @description A reference that can be used by external systems to reconcile this payment with their systems. E.g. An invoice number. (Optional) */ @@ -261,12 +269,9 @@ export interface components { * @description The URL of the payment pointer from which this quote's payment would be sent. */ paymentPointer: string; - /** @description The URL of the incoming payment or ILP Stream Connection that would be paid. */ - receiver: components["schemas"]["receiver"]; - /** @description The total amount that should be received by the receiver. */ - receiveAmount: components["schemas"]["amount"]; - /** @description The total amount that should be sent by the sender. */ - sendAmount: components["schemas"]["amount"]; + receiver: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["receiver"]; + receiveAmount: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; + sendAmount: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; /** @description The date and time when the calculated `sendAmount` is no longer valid. */ expiresAt?: string; /** @@ -275,43 +280,6 @@ export interface components { */ createdAt: string; }; - /** - * Amount - * @description All amounts in open payments are represented as a value and an asset code and scale. - * - * The `value` is an unsigned 64-bit integer amount, represented as a string. - * - * The `assetCode` is a code that indicates the underlying asset. In most cases this SHOULD be a 3-character ISO 4217 currency code. - * - * The `assetScale` indicates how the `value` has been scaled relative to the natural scale of the asset. For example, an `value` of `"1234"` with an `assetScale` of `2` represents an amount of 12.34. - */ - amount: { - /** - * Format: uint64 - * @description The amount, scaled by the given scale. - */ - value: string; - /** @description The asset code of the amount. */ - assetCode: components["schemas"]["assetCode"]; - /** @description The scale of the amount. */ - assetScale: components["schemas"]["assetScale"]; - }; - /** - * Asset code - * @description This SHOULD be an ISO4217 currency code. - */ - assetCode: string; - /** - * Asset scale - * @description The scale of amounts denoted in the corresponding asset code. - */ - assetScale: number; - /** - * Receiver - * Format: uri - * @description The URL of the incoming payment or ILP STREAM connection that is being paid. - */ - receiver: string; /** @description Pagination parameters */ pagination: | components["schemas"]["forward-pagination"] @@ -340,6 +308,18 @@ export interface components { /** @description Describes whether the data set has previous entries. */ hasPreviousPage: boolean; }; + /** + * Ed25519 Public Key + * @description A JWK representation of an Ed25519 Public Key + */ + "json-web-key": { + kid: string; + use?: "sig"; + kty: "OKP"; + crv: "Ed25519"; + /** @description The base64 url-encoded public key. */ + x: string; + }; }; responses: { /** Authorization required */ @@ -377,6 +357,19 @@ export interface operations { 404: unknown; }; }; + /** Retrieve the public keys of the Payment Pointer. */ + "get-payment-pointer-keys": { + responses: { + /** JWKS Document Found */ + 200: { + content: { + "application/json": components["schemas"]["json-web-key-set"]; + }; + }; + /** JWKS Document Not Found */ + 404: unknown; + }; + }; /** * *NB* Use server url specific to this path. * @@ -465,7 +458,7 @@ export interface operations { content: { "application/json": { /** @description The maximum amount that should be paid into the payment pointer under this incoming payment. */ - incomingAmount?: components["schemas"]["amount"]; + incomingAmount?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; /** * Format: date-time * @description The date and time when payments into the incoming payment must no longer be accepted. @@ -578,9 +571,9 @@ export interface operations { requestBody: { content: { "application/json": { - receiver: components["schemas"]["receiver"]; - receiveAmount?: components["schemas"]["amount"]; - sendAmount?: components["schemas"]["amount"]; + receiver: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["receiver"]; + receiveAmount?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; + sendAmount?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; }; }; }; @@ -699,4 +692,136 @@ export interface operations { }; } -export interface external {} +export interface external { + "https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml": { + paths: {}; + components: { + schemas: { + /** @description A description of the rights associated with this access token. */ + access: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["access-item"][]; + /** @description The access associated with the access token is described using objects that each contain multiple dimensions of access. */ + "access-item": + | external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["access-incoming"] + | external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["access-outgoing"] + | external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["access-quote"]; + /** access-incoming */ + "access-incoming": { + /** @description The type of resource request as a string. This field defines which other fields are allowed in the request object. */ + type: "incoming-payment"; + /** @description The types of actions the client instance will take at the RS as an array of strings. */ + actions: ( + | "create" + | "complete" + | "read" + | "read-all" + | "list" + | "list-all" + )[]; + /** + * Format: uri + * @description A string identifier indicating a specific resource at the RS. + */ + identifier?: string; + }; + /** access-outgoing */ + "access-outgoing": { + /** @description The type of resource request as a string. This field defines which other fields are allowed in the request object. */ + type: "outgoing-payment"; + /** @description The types of actions the client instance will take at the RS as an array of strings. */ + actions: ("create" | "read" | "read-all" | "list" | "list-all")[]; + /** + * Format: uri + * @description A string identifier indicating a specific resource at the RS. + */ + identifier: string; + limits?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["limits-outgoing"]; + }; + /** access-quote */ + "access-quote": { + /** @description The type of resource request as a string. This field defines which other fields are allowed in the request object. */ + type: "quote"; + /** @description The types of actions the client instance will take at the RS as an array of strings. */ + actions: ("create" | "read" | "read-all")[]; + }; + /** + * amount + * @description All amounts are maxima, i.e. multiple payments can be created under a grant as long as the total amounts of these payments do not exceed the maximum amount per interval as specified in the grant. + */ + amount: { + /** + * Format: uint64 + * @description The value is an unsigned 64-bit integer amount, represented as a string. + */ + value: string; + assetCode: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["assetCode"]; + assetScale: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["assetScale"]; + }; + /** + * Asset code + * @description The assetCode is a code that indicates the underlying asset. This SHOULD be an ISO4217 currency code. + */ + assetCode: string; + /** + * Asset scale + * @description The scale of amounts denoted in the corresponding asset code. + */ + assetScale: number; + /** + * Interval + * @description [ISO8601 repeating interval](https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals) + */ + interval: string; + /** + * key + * @description A key presented by value MUST be a public key. + */ + key: { + /** @description The form of proof that the client instance will use when presenting the key. */ + proof: "httpsig"; + /** @description The public key and its properties represented as a JSON Web Key [[RFC7517](https://datatracker.ietf.org/doc/html/rfc7517)]. */ + jwk: { + /** @description The cryptographic algorithm family used with the key. The only allowed value is `EdDSA`. */ + alg: "EdDSA"; + /** @description A Key ID can be used to match a specific key. */ + kid: string; + /** @description The Key Type. The only allowed value is `OKP`. */ + kty: "OKP"; + /** @description The intended use of the key. */ + use?: "sig"; + /** @description The cryptographic curve used with the key. The only allowed value is `Ed25519`. */ + crv: "Ed25519"; + /** @description Public key encoded using the `base64url` encoding. */ + x: string; + /** @description Array of allowed operations this key may be used for. */ + key_ops?: ("sign" | "verify")[]; + /** @description UNIX timestamp indicating the earliest this key may be used. */ + nbf?: number; + /** @description UNIX timestamp indicating the latest this key may be used. */ + exp?: number; + /** @description The revocation status of the key. */ + revoked?: boolean; + }; + }; + /** + * limits-outgoing + * @description Open Payments specific property that defines the limits under which outgoing payments can be created. + */ + "limits-outgoing": Partial & { + receiver?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["receiver"]; + sendAmount?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; + receiveAmount?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["amount"]; + interval?: external["https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/shared/schemas.yaml"]["components"]["schemas"]["interval"]; + }; + "list-actions": "list" | "list-all"; + "read-actions": "read" | "read-all"; + /** + * Receiver + * Format: uri + * @description The URL of the incoming payment or ILP STREAM connection that is being paid. + */ + receiver: string; + }; + }; + operations: {}; + }; +} diff --git a/packages/open-payments/src/types.ts b/packages/open-payments/src/types.ts index a282d9b169..7f204b10c1 100644 --- a/packages/open-payments/src/types.ts +++ b/packages/open-payments/src/types.ts @@ -1,4 +1,5 @@ import { components } from './generated/types' -export type IncomingPayment = components['schemas']['incoming-payment'] +export type IncomingPayment = + components['schemas']['incoming-payment-with-connection'] export type ILPStreamConnection = components['schemas']['ilp-stream-connection'] From f8c184654716b80fc43876e9bef9e98a3c3d53b5 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Mon, 24 Oct 2022 13:55:40 +0200 Subject: [PATCH 20/33] feat(open-payments): build open api package on build step --- packages/open-payments/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/open-payments/package.json b/packages/open-payments/package.json index 2caaef6075..60464f9807 100644 --- a/packages/open-payments/package.json +++ b/packages/open-payments/package.json @@ -7,7 +7,8 @@ "dist/**/*" ], "scripts": { - "build": "pnpm clean && tsc --build tsconfig.json", + "build:deps": "pnpm --filter openapi build", + "build": "pnpm build:deps && pnpm clean && tsc --build tsconfig.json", "clean": "rm -fr dist/", "generate:types": "npx ts-node scripts/generate-types.ts", "prepack": "pnpm build", From e11ec65f35a8caa282d5f757d6f7a6af5b7de7ba Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Mon, 24 Oct 2022 14:01:18 +0200 Subject: [PATCH 21/33] feat(open-payments): building open-api package during workflow --- .github/workflows/lint_test_build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint_test_build.yml b/.github/workflows/lint_test_build.yml index 9a55454eec..c5848f174e 100644 --- a/.github/workflows/lint_test_build.yml +++ b/.github/workflows/lint_test_build.yml @@ -64,6 +64,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: ./.github/workflows/rafiki/env-setup + - run: pnpm --filter openapi build - run: pnpm --filter open-payments test build: From 0dad54d6da56ded62df39f5c3d495150f8da1d42 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Mon, 24 Oct 2022 14:04:45 +0200 Subject: [PATCH 22/33] Revert "feat(open-payments): building open-api package during workflow" This reverts commit e11ec65f35a8caa282d5f757d6f7a6af5b7de7ba. --- .github/workflows/lint_test_build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lint_test_build.yml b/.github/workflows/lint_test_build.yml index c5848f174e..9a55454eec 100644 --- a/.github/workflows/lint_test_build.yml +++ b/.github/workflows/lint_test_build.yml @@ -64,7 +64,6 @@ jobs: steps: - uses: actions/checkout@v2 - uses: ./.github/workflows/rafiki/env-setup - - run: pnpm --filter openapi build - run: pnpm --filter open-payments test build: From 25d26171aae1047545fa2a3e98286c073616381c Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Mon, 24 Oct 2022 14:06:24 +0200 Subject: [PATCH 23/33] feat(open-payments): building open-api package during workflow --- .github/workflows/lint_test_build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint_test_build.yml b/.github/workflows/lint_test_build.yml index 9a55454eec..c5848f174e 100644 --- a/.github/workflows/lint_test_build.yml +++ b/.github/workflows/lint_test_build.yml @@ -64,6 +64,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: ./.github/workflows/rafiki/env-setup + - run: pnpm --filter openapi build - run: pnpm --filter open-payments test build: From 4552188adb0cdf69780a8196a345c772f15d0fa7 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Mon, 24 Oct 2022 16:50:29 +0200 Subject: [PATCH 24/33] feat(open-payments): update naming --- packages/open-payments/src/client/requests.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/open-payments/src/client/requests.ts b/packages/open-payments/src/client/requests.ts index 909214dbc7..6dbb5c474f 100644 --- a/packages/open-payments/src/client/requests.ts +++ b/packages/open-payments/src/client/requests.ts @@ -11,7 +11,7 @@ export interface GetArgs { export const get = async ( axios: AxiosInstance, args: GetArgs, - responseValidator: ValidateFunction + openApiResponseValidator: ValidateFunction ): Promise => { const { url, accessToken } = args @@ -26,7 +26,7 @@ export const get = async ( : {} }) - if (!responseValidator(data)) { + if (!openApiResponseValidator(data)) { const errorMessage = 'Failed to validate OpenApi response' console.log(errorMessage, { url, From 85db13611f153ec1cd5fde078d0c2aea1f139e7b Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Tue, 25 Oct 2022 14:24:58 +0200 Subject: [PATCH 25/33] feat(open-payments): instantiate validator functions once --- .../src/client/ilp-stream-connection.test.ts | 20 ++++--------- .../src/client/ilp-stream-connection.ts | 22 ++++++++------ .../src/client/incoming-payment.test.ts | 20 ++++--------- .../src/client/incoming-payment.ts | 22 ++++++++------ packages/open-payments/src/client/index.ts | 29 +++++++++---------- 5 files changed, 49 insertions(+), 64 deletions(-) diff --git a/packages/open-payments/src/client/ilp-stream-connection.test.ts b/packages/open-payments/src/client/ilp-stream-connection.test.ts index f347ad6af1..17a617fe8e 100644 --- a/packages/open-payments/src/client/ilp-stream-connection.test.ts +++ b/packages/open-payments/src/client/ilp-stream-connection.test.ts @@ -1,14 +1,9 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -import { getILPStreamConnection } from './ilp-stream-connection' +import { createILPStreamConnectionRoutes } from './ilp-stream-connection' import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' import { createAxiosInstance } from './requests' import config from '../config' -jest.mock('./requests', () => ({ - ...jest.requireActual('./requests'), - get: jest.fn() -})) - describe('ilp-stream-connection', (): void => { let openApi: OpenAPI @@ -18,17 +13,12 @@ describe('ilp-stream-connection', (): void => { const axiosInstance = createAxiosInstance() - describe('getILPStreamConnection', (): void => { + describe('createILPStreamConnectionRoutes', (): void => { test('calls createResponseValidator properly', async (): Promise => { - const createResponseValidatorSpy = jest.spyOn( - openApi, - 'createResponseValidator' - ) + jest.spyOn(openApi, 'createResponseValidator') - await getILPStreamConnection(axiosInstance, openApi, { - url: 'http://localhost:1000/incoming-payment' - }) - expect(createResponseValidatorSpy).toHaveBeenCalledWith({ + createILPStreamConnectionRoutes(axiosInstance, openApi) + expect(openApi.createResponseValidator).toHaveBeenCalledWith({ path: '/connections/{id}', method: HttpMethod.GET }) diff --git a/packages/open-payments/src/client/ilp-stream-connection.ts b/packages/open-payments/src/client/ilp-stream-connection.ts index fe8db4b767..43d8370c5f 100644 --- a/packages/open-payments/src/client/ilp-stream-connection.ts +++ b/packages/open-payments/src/client/ilp-stream-connection.ts @@ -3,17 +3,21 @@ import { OpenAPI, HttpMethod } from 'openapi' import { getPath, ILPStreamConnection } from '../types' import { GetArgs, get } from './requests' -export const getILPStreamConnection = async ( +export interface ILPStreamConnectionRoutes { + get(args: GetArgs): Promise +} + +export const createILPStreamConnectionRoutes = ( axios: AxiosInstance, - openApi: OpenAPI, - args: GetArgs -): Promise => { - return get( - axios, - args, - openApi.createResponseValidator({ + openApi: OpenAPI +): ILPStreamConnectionRoutes => { + const getILPStreamConnectionValidator = + openApi.createResponseValidator({ path: getPath('/connections/{id}'), method: HttpMethod.GET }) - ) + + return { + get: (args: GetArgs) => get(axios, args, getILPStreamConnectionValidator) + } } diff --git a/packages/open-payments/src/client/incoming-payment.test.ts b/packages/open-payments/src/client/incoming-payment.test.ts index ac29c328ef..add7ec5c4e 100644 --- a/packages/open-payments/src/client/incoming-payment.test.ts +++ b/packages/open-payments/src/client/incoming-payment.test.ts @@ -1,14 +1,9 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -import { getIncomingPayment } from './incoming-payment' +import { createIncomingPaymentRoutes } from './incoming-payment' import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' import { createAxiosInstance } from './requests' import config from '../config' -jest.mock('./requests', () => ({ - ...jest.requireActual('./requests'), - get: jest.fn() -})) - describe('incoming-payment', (): void => { let openApi: OpenAPI @@ -18,17 +13,12 @@ describe('incoming-payment', (): void => { const axiosInstance = createAxiosInstance() - describe('getIncomingPayment', (): void => { + describe('createIncomingPaymentRoutes', (): void => { test('calls createResponseValidator properly', async (): Promise => { - const createResponseValidatorSpy = jest.spyOn( - openApi, - 'createResponseValidator' - ) + jest.spyOn(openApi, 'createResponseValidator') - await getIncomingPayment(axiosInstance, openApi, { - url: 'http://localhost:1000/incoming-payment' - }) - expect(createResponseValidatorSpy).toHaveBeenCalledWith({ + createIncomingPaymentRoutes(axiosInstance, openApi) + expect(openApi.createResponseValidator).toHaveBeenCalledWith({ path: '/incoming-payments/{id}', method: HttpMethod.GET }) diff --git a/packages/open-payments/src/client/incoming-payment.ts b/packages/open-payments/src/client/incoming-payment.ts index 6303ad8fa8..b827238cba 100644 --- a/packages/open-payments/src/client/incoming-payment.ts +++ b/packages/open-payments/src/client/incoming-payment.ts @@ -3,17 +3,21 @@ import { OpenAPI, HttpMethod } from 'openapi' import { IncomingPayment, getPath } from '../types' import { GetArgs, get } from './requests' -export const getIncomingPayment = async ( +export interface IncomingPaymentRoutes { + get(args: GetArgs): Promise +} + +export const createIncomingPaymentRoutes = ( axios: AxiosInstance, - openApi: OpenAPI, - args: GetArgs -): Promise => { - return get( - axios, - args, - openApi.createResponseValidator({ + openApi: OpenAPI +): IncomingPaymentRoutes => { + const getIncomingPaymentValidator = + openApi.createResponseValidator({ path: getPath('/incoming-payments/{id}'), method: HttpMethod.GET }) - ) + + return { + get: (args: GetArgs) => get(axios, args, getIncomingPaymentValidator) + } } diff --git a/packages/open-payments/src/client/index.ts b/packages/open-payments/src/client/index.ts index ad47e9aa3f..7d6f225d43 100644 --- a/packages/open-payments/src/client/index.ts +++ b/packages/open-payments/src/client/index.ts @@ -1,22 +1,23 @@ import { createOpenAPI } from 'openapi' -import { ILPStreamConnection, IncomingPayment } from '../types' import config from '../config' -import { getIncomingPayment } from './incoming-payment' -import { getILPStreamConnection } from './ilp-stream-connection' -import { createAxiosInstance, GetArgs } from './requests' +import { + createIncomingPaymentRoutes, + IncomingPaymentRoutes +} from './incoming-payment' +import { + createILPStreamConnectionRoutes, + ILPStreamConnectionRoutes +} from './ilp-stream-connection' +import { createAxiosInstance } from './requests' export interface CreateOpenPaymentClientArgs { timeout?: number } export interface OpenPaymentsClient { - incomingPayment: { - get(args: GetArgs): Promise - } - ilpStreamConnection: { - get(args: GetArgs): Promise - } + incomingPayment: IncomingPaymentRoutes + ilpStreamConnection: ILPStreamConnectionRoutes } export const createClient = async ( @@ -26,11 +27,7 @@ export const createClient = async ( const openApi = await createOpenAPI(config.OPEN_PAYMENTS_OPEN_API_URL) return { - incomingPayment: { - get: (args: GetArgs) => getIncomingPayment(axios, openApi, args) - }, - ilpStreamConnection: { - get: (args: GetArgs) => getILPStreamConnection(axios, openApi, args) - } + incomingPayment: createIncomingPaymentRoutes(axios, openApi), + ilpStreamConnection: createILPStreamConnectionRoutes(axios, openApi) } } From dc4aa0fd4f718955292c03e05db0f20d11a66410 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Tue, 25 Oct 2022 14:26:20 +0200 Subject: [PATCH 26/33] chore(openapi): add docs for usage --- packages/openapi/README.md | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/openapi/README.md b/packages/openapi/README.md index 9387c5f328..074cf7d703 100644 --- a/packages/openapi/README.md +++ b/packages/openapi/README.md @@ -1,5 +1,7 @@ # OpenAPI 3.1 Validator +This package exposes functionality to validate requests and responses according to a given OpenAPI 3.1 schema. + ## Local Development ### Building @@ -15,3 +17,45 @@ From the monorepo root directory: ```shell pnpm --filter openapi test ``` + +## Usage + +First, instantiate an `OpenAPI` validator object with a reference to your OpenAPI spec: + +```ts +const openApi = await createOpenAPI(OPEN_API_URL) +``` + +Then, responses and requests validators can be created and used as such: + +```ts +const validateRequest = openApi.createRequestValidator({ + path: '/resource/{id}', + method: HttpMethod.GET +}) + +validateRequest(data) // true or false + +const validateResponse = openApi.createResponseValidator({ + path: '/resource/{id}', + method: HttpMethod.GET +}) + +validateResponse(data) // true or false +``` + +> **Note** +> The underlying response & request validator [packages](https://github.com/kogosoftwarellc/open-api/tree/master/packages) use the [Ajv schema validator](https://ajv.js.org) library. When a request and a validator is created, a `new Ajv()` instance is also created. However, Avj [recommends](https://ajv.js.org/guide/managing-schemas.html#compiling-during-initialization) instantiating once at initialization. This means validators (`openApi.createRequestValidator` and `openApi.createResponseValidator`) should also be instantiated once during the lifecycle of the applcation to avoid any issues. + +Likewise, you can validate both requests and responses in a middleware, using the `createValidatorMiddleware` method: + +```ts +const openApi = await createOpenAPI(OPEN_API_URL) +const router = new SomeRouter() +router.get( + '/example', + createValidatorMiddleware(openApi, { + path: '/example', + method: HttpMethod.GET +}) +``` From c927291c718e4ede720c104afef15fb60fe9e71c Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Tue, 25 Oct 2022 14:37:47 +0200 Subject: [PATCH 27/33] chore(openapi): update docs --- packages/openapi/README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/openapi/README.md b/packages/openapi/README.md index 074cf7d703..d7ec561302 100644 --- a/packages/openapi/README.md +++ b/packages/openapi/README.md @@ -26,7 +26,7 @@ First, instantiate an `OpenAPI` validator object with a reference to your OpenAP const openApi = await createOpenAPI(OPEN_API_URL) ``` -Then, responses and requests validators can be created and used as such: +Then, validate requests and responses as such: ```ts const validateRequest = openApi.createRequestValidator({ @@ -45,17 +45,23 @@ validateResponse(data) // true or false ``` > **Note** -> The underlying response & request validator [packages](https://github.com/kogosoftwarellc/open-api/tree/master/packages) use the [Ajv schema validator](https://ajv.js.org) library. When a request and a validator is created, a `new Ajv()` instance is also created. However, Avj [recommends](https://ajv.js.org/guide/managing-schemas.html#compiling-during-initialization) instantiating once at initialization. This means validators (`openApi.createRequestValidator` and `openApi.createResponseValidator`) should also be instantiated once during the lifecycle of the applcation to avoid any issues. +> +> The underlying response & request validator [packages](https://github.com/kogosoftwarellc/open-api/tree/master/packages) use the [Ajv schema validator](https://ajv.js.org) library. Each time validators are created via `createRequestValidator` and `createResponseValidator`, a `new Ajv()` instance is also [created](https://github.com/kogosoftwarellc/open-api/blob/master/packages/openapi-response-validator/index.ts). Since Ajv [recommends](https://ajv.js.org/guide/managing-schemas.html#compiling-during-initialization) instantiating once at initialization, these validators should also be instantiated just once during the lifecycle of the application to avoid any issues. -Likewise, you can validate both requests and responses in a middleware, using the `createValidatorMiddleware` method: + + +
+ +Likewise, you can validate both requests and responses inside a middleware method, using `createValidatorMiddleware`: ```ts const openApi = await createOpenAPI(OPEN_API_URL) const router = new SomeRouter() router.get( - '/example', + '/resource/{id}', createValidatorMiddleware(openApi, { - path: '/example', - method: HttpMethod.GET -}) + path: '/resource/{id}', + method: HttpMethod.GET + }) +) ``` From 3ed946b98048fa5d36f3b8e4ab891419a165289c Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Tue, 25 Oct 2022 14:39:32 +0200 Subject: [PATCH 28/33] chore(openapi): prettify docs --- packages/openapi/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/openapi/README.md b/packages/openapi/README.md index d7ec561302..13a4e06902 100644 --- a/packages/openapi/README.md +++ b/packages/openapi/README.md @@ -48,8 +48,6 @@ validateResponse(data) // true or false > > The underlying response & request validator [packages](https://github.com/kogosoftwarellc/open-api/tree/master/packages) use the [Ajv schema validator](https://ajv.js.org) library. Each time validators are created via `createRequestValidator` and `createResponseValidator`, a `new Ajv()` instance is also [created](https://github.com/kogosoftwarellc/open-api/blob/master/packages/openapi-response-validator/index.ts). Since Ajv [recommends](https://ajv.js.org/guide/managing-schemas.html#compiling-during-initialization) instantiating once at initialization, these validators should also be instantiated just once during the lifecycle of the application to avoid any issues. - -
Likewise, you can validate both requests and responses inside a middleware method, using `createValidatorMiddleware`: @@ -58,10 +56,10 @@ Likewise, you can validate both requests and responses inside a middleware metho const openApi = await createOpenAPI(OPEN_API_URL) const router = new SomeRouter() router.get( - '/resource/{id}', - createValidatorMiddleware(openApi, { - path: '/resource/{id}', - method: HttpMethod.GET - }) + '/resource/{id}', + createValidatorMiddleware(openApi, { + path: '/resource/{id}', + method: HttpMethod.GET + }) ) ``` From 347075a1435449b352bb4eae8ba51c5f3585bcef Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Tue, 25 Oct 2022 20:06:02 +0200 Subject: [PATCH 29/33] chore(openapi): update docs --- packages/openapi/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/openapi/README.md b/packages/openapi/README.md index 13a4e06902..102b4f7d07 100644 --- a/packages/openapi/README.md +++ b/packages/openapi/README.md @@ -23,7 +23,7 @@ pnpm --filter openapi test First, instantiate an `OpenAPI` validator object with a reference to your OpenAPI spec: ```ts -const openApi = await createOpenAPI(OPEN_API_URL) +const openApi = await createOpenAPI(OPEN_API_URL_OR_FILE_PATH) ``` Then, validate requests and responses as such: @@ -34,14 +34,14 @@ const validateRequest = openApi.createRequestValidator({ method: HttpMethod.GET }) -validateRequest(data) // true or false +validateRequest(request) // true or false const validateResponse = openApi.createResponseValidator({ path: '/resource/{id}', method: HttpMethod.GET }) -validateResponse(data) // true or false +validateResponse(response.body) // true or false ``` > **Note** From 90bc6b020ee7790e448f3dba805841859b90de03 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Tue, 25 Oct 2022 21:00:03 +0200 Subject: [PATCH 30/33] feat(open-payments): adding logger as dependency --- packages/open-payments/package.json | 3 ++- .../src/client/ilp-stream-connection.test.ts | 4 ++- .../src/client/ilp-stream-connection.ts | 12 +++++---- .../src/client/incoming-payment.test.ts | 8 +++++- .../src/client/incoming-payment.ts | 12 +++++---- packages/open-payments/src/client/index.ts | 25 +++++++++++++++---- .../open-payments/src/client/requests.test.ts | 9 ++++--- packages/open-payments/src/client/requests.ts | 11 ++++---- packages/open-payments/src/test/helpers.ts | 5 ++++ packages/open-payments/tsconfig.json | 2 +- pnpm-lock.yaml | 2 ++ 11 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 packages/open-payments/src/test/helpers.ts diff --git a/packages/open-payments/package.json b/packages/open-payments/package.json index 60464f9807..31a38e9909 100644 --- a/packages/open-payments/package.json +++ b/packages/open-payments/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "axios": "^1.1.2", - "openapi": "workspace:../openapi" + "openapi": "workspace:../openapi", + "pino": "^8.4.2" } } diff --git a/packages/open-payments/src/client/ilp-stream-connection.test.ts b/packages/open-payments/src/client/ilp-stream-connection.test.ts index 17a617fe8e..dffb6078b5 100644 --- a/packages/open-payments/src/client/ilp-stream-connection.test.ts +++ b/packages/open-payments/src/client/ilp-stream-connection.test.ts @@ -3,6 +3,7 @@ import { createILPStreamConnectionRoutes } from './ilp-stream-connection' import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' import { createAxiosInstance } from './requests' import config from '../config' +import { silentLogger } from '../test/helpers' describe('ilp-stream-connection', (): void => { let openApi: OpenAPI @@ -12,12 +13,13 @@ describe('ilp-stream-connection', (): void => { }) const axiosInstance = createAxiosInstance() + const logger = silentLogger describe('createILPStreamConnectionRoutes', (): void => { test('calls createResponseValidator properly', async (): Promise => { jest.spyOn(openApi, 'createResponseValidator') - createILPStreamConnectionRoutes(axiosInstance, openApi) + createILPStreamConnectionRoutes({ axiosInstance, openApi, logger }) expect(openApi.createResponseValidator).toHaveBeenCalledWith({ path: '/connections/{id}', method: HttpMethod.GET diff --git a/packages/open-payments/src/client/ilp-stream-connection.ts b/packages/open-payments/src/client/ilp-stream-connection.ts index 43d8370c5f..f36f0c9025 100644 --- a/packages/open-payments/src/client/ilp-stream-connection.ts +++ b/packages/open-payments/src/client/ilp-stream-connection.ts @@ -1,5 +1,5 @@ -import { AxiosInstance } from 'axios' -import { OpenAPI, HttpMethod } from 'openapi' +import { HttpMethod } from 'openapi' +import { ClientDeps } from '.' import { getPath, ILPStreamConnection } from '../types' import { GetArgs, get } from './requests' @@ -8,9 +8,10 @@ export interface ILPStreamConnectionRoutes { } export const createILPStreamConnectionRoutes = ( - axios: AxiosInstance, - openApi: OpenAPI + clientDeps: ClientDeps ): ILPStreamConnectionRoutes => { + const { axiosInstance, openApi, logger } = clientDeps + const getILPStreamConnectionValidator = openApi.createResponseValidator({ path: getPath('/connections/{id}'), @@ -18,6 +19,7 @@ export const createILPStreamConnectionRoutes = ( }) return { - get: (args: GetArgs) => get(axios, args, getILPStreamConnectionValidator) + get: (args: GetArgs) => + get({ axiosInstance, logger }, args, getILPStreamConnectionValidator) } } diff --git a/packages/open-payments/src/client/incoming-payment.test.ts b/packages/open-payments/src/client/incoming-payment.test.ts index add7ec5c4e..02d4967527 100644 --- a/packages/open-payments/src/client/incoming-payment.test.ts +++ b/packages/open-payments/src/client/incoming-payment.test.ts @@ -3,6 +3,7 @@ import { createIncomingPaymentRoutes } from './incoming-payment' import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' import { createAxiosInstance } from './requests' import config from '../config' +import { silentLogger } from '../test/helpers' describe('incoming-payment', (): void => { let openApi: OpenAPI @@ -12,12 +13,17 @@ describe('incoming-payment', (): void => { }) const axiosInstance = createAxiosInstance() + const logger = silentLogger describe('createIncomingPaymentRoutes', (): void => { test('calls createResponseValidator properly', async (): Promise => { jest.spyOn(openApi, 'createResponseValidator') - createIncomingPaymentRoutes(axiosInstance, openApi) + createIncomingPaymentRoutes({ + axiosInstance, + openApi, + logger + }) expect(openApi.createResponseValidator).toHaveBeenCalledWith({ path: '/incoming-payments/{id}', method: HttpMethod.GET diff --git a/packages/open-payments/src/client/incoming-payment.ts b/packages/open-payments/src/client/incoming-payment.ts index b827238cba..b787fff3c6 100644 --- a/packages/open-payments/src/client/incoming-payment.ts +++ b/packages/open-payments/src/client/incoming-payment.ts @@ -1,5 +1,5 @@ -import { AxiosInstance } from 'axios' -import { OpenAPI, HttpMethod } from 'openapi' +import { HttpMethod } from 'openapi' +import { ClientDeps } from '.' import { IncomingPayment, getPath } from '../types' import { GetArgs, get } from './requests' @@ -8,9 +8,10 @@ export interface IncomingPaymentRoutes { } export const createIncomingPaymentRoutes = ( - axios: AxiosInstance, - openApi: OpenAPI + clientDeps: ClientDeps ): IncomingPaymentRoutes => { + const { axiosInstance, openApi, logger } = clientDeps + const getIncomingPaymentValidator = openApi.createResponseValidator({ path: getPath('/incoming-payments/{id}'), @@ -18,6 +19,7 @@ export const createIncomingPaymentRoutes = ( }) return { - get: (args: GetArgs) => get(axios, args, getIncomingPaymentValidator) + get: (args: GetArgs) => + get({ axiosInstance, logger }, args, getIncomingPaymentValidator) } } diff --git a/packages/open-payments/src/client/index.ts b/packages/open-payments/src/client/index.ts index 7d6f225d43..bc57d2f191 100644 --- a/packages/open-payments/src/client/index.ts +++ b/packages/open-payments/src/client/index.ts @@ -1,5 +1,5 @@ -import { createOpenAPI } from 'openapi' - +import { createOpenAPI, OpenAPI } from 'openapi' +import createLogger, { Logger, LevelWithSilent as LogLevel } from 'pino' import config from '../config' import { createIncomingPaymentRoutes, @@ -10,9 +10,18 @@ import { ILPStreamConnectionRoutes } from './ilp-stream-connection' import { createAxiosInstance } from './requests' +import { AxiosInstance } from 'axios' export interface CreateOpenPaymentClientArgs { timeout?: number + logger?: Logger + loggerLevel?: LogLevel +} + +export interface ClientDeps { + axiosInstance: AxiosInstance + openApi: OpenAPI + logger: Logger } export interface OpenPaymentsClient { @@ -23,11 +32,17 @@ export interface OpenPaymentsClient { export const createClient = async ( args?: CreateOpenPaymentClientArgs ): Promise => { - const axios = createAxiosInstance(args) + const axiosInstance = createAxiosInstance(args) const openApi = await createOpenAPI(config.OPEN_PAYMENTS_OPEN_API_URL) + const logger = + args.logger ?? + createLogger({ + level: args.loggerLevel ?? 'info' + }) + const deps = { axiosInstance, openApi, logger } return { - incomingPayment: createIncomingPaymentRoutes(axios, openApi), - ilpStreamConnection: createILPStreamConnectionRoutes(axios, openApi) + incomingPayment: createIncomingPaymentRoutes(deps), + ilpStreamConnection: createILPStreamConnectionRoutes(deps) } } diff --git a/packages/open-payments/src/client/requests.test.ts b/packages/open-payments/src/client/requests.test.ts index 08f911f336..b68bebf93d 100644 --- a/packages/open-payments/src/client/requests.test.ts +++ b/packages/open-payments/src/client/requests.test.ts @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import { createAxiosInstance, get } from './requests' import nock from 'nock' +import { silentLogger } from '../test/helpers' describe('requests', (): void => { - jest.spyOn(console, 'log').mockImplementation(() => {}) + const logger = silentLogger describe('createAxiosInstance', (): void => { test('sets timeout properly', async (): Promise => { @@ -30,7 +31,7 @@ describe('requests', (): void => { nock(baseUrl).get('/incoming-payment').reply(200) await get( - axiosInstance, + { axiosInstance, logger }, { url: `${baseUrl}/incoming-payment`, accessToken: 'accessToken' @@ -54,7 +55,7 @@ describe('requests', (): void => { nock(baseUrl).get('/incoming-payment').reply(200) await get( - axiosInstance, + { axiosInstance, logger }, { url: `${baseUrl}/incoming-payment` }, @@ -74,7 +75,7 @@ describe('requests', (): void => { await expect( get( - axiosInstance, + { axiosInstance, logger }, { url: `${baseUrl}/incoming-payment` }, diff --git a/packages/open-payments/src/client/requests.ts b/packages/open-payments/src/client/requests.ts index 6dbb5c474f..96fcec4936 100644 --- a/packages/open-payments/src/client/requests.ts +++ b/packages/open-payments/src/client/requests.ts @@ -1,6 +1,6 @@ import axios, { AxiosInstance } from 'axios' import { ValidateFunction } from 'openapi' -import { CreateOpenPaymentClientArgs } from '.' +import { ClientDeps, CreateOpenPaymentClientArgs } from '.' import config from '../config' export interface GetArgs { @@ -9,14 +9,15 @@ export interface GetArgs { } export const get = async ( - axios: AxiosInstance, + clientDeps: Pick, args: GetArgs, openApiResponseValidator: ValidateFunction ): Promise => { + const { axiosInstance, logger } = clientDeps const { url, accessToken } = args try { - const { data } = await axios.get(url, { + const { data } = await axiosInstance.get(url, { headers: accessToken ? { Authorization: `GNAP ${accessToken}`, @@ -28,7 +29,7 @@ export const get = async ( if (!openApiResponseValidator(data)) { const errorMessage = 'Failed to validate OpenApi response' - console.log(errorMessage, { + logger.error(errorMessage, { url, data: JSON.stringify(data) }) @@ -38,7 +39,7 @@ export const get = async ( return data } catch (error) { - console.log('Error when making Open Payments GET request', { + logger.error('Error when making Open Payments GET request', { errorMessage: error?.message, url }) diff --git a/packages/open-payments/src/test/helpers.ts b/packages/open-payments/src/test/helpers.ts new file mode 100644 index 0000000000..b657490d1d --- /dev/null +++ b/packages/open-payments/src/test/helpers.ts @@ -0,0 +1,5 @@ +import createLogger from 'pino' + +export const silentLogger = createLogger({ + level: 'silent' +}) diff --git a/packages/open-payments/tsconfig.json b/packages/open-payments/tsconfig.json index cf98f1692c..700f4d60dc 100644 --- a/packages/open-payments/tsconfig.json +++ b/packages/open-payments/tsconfig.json @@ -7,5 +7,5 @@ "declaration": true }, "include": ["src/**/*"], - "exclude": ["**/*.test.ts", "src/scripts/*"] + "exclude": ["**/*.test.ts", "src/scripts/*", "src/test/*"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4b2580a37..f2e11e9940 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -312,11 +312,13 @@ importers: nock: ^13.2.9 openapi: workspace:../openapi openapi-typescript: ^4.5.0 + pino: ^8.4.2 ts-node: ^10.7.0 typescript: ^4.3.0 dependencies: axios: 1.1.2 openapi: link:../openapi + pino: 8.4.2 devDependencies: '@types/node': 18.7.13 nock: 13.2.9 From 3129f2aed55163fda092c6f396687c0fdce920a0 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Tue, 25 Oct 2022 21:36:06 +0200 Subject: [PATCH 31/33] feat(open-payments): updating logging & error logic --- .../src/client/ilp-stream-connection.test.ts | 5 ++-- .../src/client/incoming-payment.test.ts | 5 ++-- packages/open-payments/src/client/index.ts | 14 +++++----- .../open-payments/src/client/requests.test.ts | 10 ++++--- packages/open-payments/src/client/requests.ts | 26 +++++++------------ packages/open-payments/src/config.ts | 2 +- packages/open-payments/src/test/helpers.ts | 3 +++ 7 files changed, 31 insertions(+), 34 deletions(-) diff --git a/packages/open-payments/src/client/ilp-stream-connection.test.ts b/packages/open-payments/src/client/ilp-stream-connection.test.ts index dffb6078b5..c22839783c 100644 --- a/packages/open-payments/src/client/ilp-stream-connection.test.ts +++ b/packages/open-payments/src/client/ilp-stream-connection.test.ts @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import { createILPStreamConnectionRoutes } from './ilp-stream-connection' import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' -import { createAxiosInstance } from './requests' import config from '../config' -import { silentLogger } from '../test/helpers' +import { defaultAxiosInstance, silentLogger } from '../test/helpers' describe('ilp-stream-connection', (): void => { let openApi: OpenAPI @@ -12,7 +11,7 @@ describe('ilp-stream-connection', (): void => { openApi = await createOpenAPI(config.OPEN_PAYMENTS_OPEN_API_URL) }) - const axiosInstance = createAxiosInstance() + const axiosInstance = defaultAxiosInstance const logger = silentLogger describe('createILPStreamConnectionRoutes', (): void => { diff --git a/packages/open-payments/src/client/incoming-payment.test.ts b/packages/open-payments/src/client/incoming-payment.test.ts index 02d4967527..764025f562 100644 --- a/packages/open-payments/src/client/incoming-payment.test.ts +++ b/packages/open-payments/src/client/incoming-payment.test.ts @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import { createIncomingPaymentRoutes } from './incoming-payment' import { OpenAPI, HttpMethod, createOpenAPI } from 'openapi' -import { createAxiosInstance } from './requests' import config from '../config' -import { silentLogger } from '../test/helpers' +import { defaultAxiosInstance, silentLogger } from '../test/helpers' describe('incoming-payment', (): void => { let openApi: OpenAPI @@ -12,7 +11,7 @@ describe('incoming-payment', (): void => { openApi = await createOpenAPI(config.OPEN_PAYMENTS_OPEN_API_URL) }) - const axiosInstance = createAxiosInstance() + const axiosInstance = defaultAxiosInstance const logger = silentLogger describe('createIncomingPaymentRoutes', (): void => { diff --git a/packages/open-payments/src/client/index.ts b/packages/open-payments/src/client/index.ts index bc57d2f191..306b3d7865 100644 --- a/packages/open-payments/src/client/index.ts +++ b/packages/open-payments/src/client/index.ts @@ -13,9 +13,8 @@ import { createAxiosInstance } from './requests' import { AxiosInstance } from 'axios' export interface CreateOpenPaymentClientArgs { - timeout?: number + requestTimeoutMs?: number logger?: Logger - loggerLevel?: LogLevel } export interface ClientDeps { @@ -32,13 +31,12 @@ export interface OpenPaymentsClient { export const createClient = async ( args?: CreateOpenPaymentClientArgs ): Promise => { - const axiosInstance = createAxiosInstance(args) + const axiosInstance = createAxiosInstance({ + requestTimeoutMs: + args?.requestTimeoutMs ?? config.DEFAULT_REQUEST_TIMEOUT_MS + }) const openApi = await createOpenAPI(config.OPEN_PAYMENTS_OPEN_API_URL) - const logger = - args.logger ?? - createLogger({ - level: args.loggerLevel ?? 'info' - }) + const logger = args?.logger ?? createLogger() const deps = { axiosInstance, openApi, logger } return { diff --git a/packages/open-payments/src/client/requests.test.ts b/packages/open-payments/src/client/requests.test.ts index b68bebf93d..e32128f2d5 100644 --- a/packages/open-payments/src/client/requests.test.ts +++ b/packages/open-payments/src/client/requests.test.ts @@ -8,17 +8,21 @@ describe('requests', (): void => { describe('createAxiosInstance', (): void => { test('sets timeout properly', async (): Promise => { - expect(createAxiosInstance({ timeout: 1000 }).defaults.timeout).toBe(1000) + expect( + createAxiosInstance({ requestTimeoutMs: 1000 }).defaults.timeout + ).toBe(1000) }) test('sets Content-Type header properly', async (): Promise => { expect( - createAxiosInstance().defaults.headers.common['Content-Type'] + createAxiosInstance({ requestTimeoutMs: 0 }).defaults.headers.common[ + 'Content-Type' + ] ).toBe('application/json') }) }) describe('get', (): void => { - const axiosInstance = createAxiosInstance() + const axiosInstance = createAxiosInstance({ requestTimeoutMs: 0 }) const baseUrl = 'http://localhost:1000' const successfulValidator = (data: unknown): data is unknown => true const failedValidator = (data: unknown): data is unknown => false diff --git a/packages/open-payments/src/client/requests.ts b/packages/open-payments/src/client/requests.ts index 96fcec4936..bc7448f3fa 100644 --- a/packages/open-payments/src/client/requests.ts +++ b/packages/open-payments/src/client/requests.ts @@ -1,7 +1,6 @@ -import axios, { AxiosInstance } from 'axios' +import axios, { AxiosError, AxiosInstance } from 'axios' import { ValidateFunction } from 'openapi' -import { ClientDeps, CreateOpenPaymentClientArgs } from '.' -import config from '../config' +import { ClientDeps } from '.' export interface GetArgs { url: string @@ -29,30 +28,25 @@ export const get = async ( if (!openApiResponseValidator(data)) { const errorMessage = 'Failed to validate OpenApi response' - logger.error(errorMessage, { - url, - data: JSON.stringify(data) - }) + logger.error({ data: JSON.stringify(data), url }, errorMessage) throw new Error(errorMessage) } return data } catch (error) { - logger.error('Error when making Open Payments GET request', { - errorMessage: error?.message, - url - }) + const errorMessage = 'Error when making Open Payments GET request' + logger.error({ errorMessage: error?.message, url }, errorMessage) - throw error + throw new Error(errorMessage) } } -export const createAxiosInstance = ( - args?: CreateOpenPaymentClientArgs -): AxiosInstance => { +export const createAxiosInstance = (args: { + requestTimeoutMs: number +}): AxiosInstance => { const axiosInstance = axios.create({ - timeout: args?.timeout ?? config.DEFAULT_REQUEST_TIMEOUT + timeout: args.requestTimeoutMs }) axiosInstance.defaults.headers.common['Content-Type'] = 'application/json' diff --git a/packages/open-payments/src/config.ts b/packages/open-payments/src/config.ts index 7c16ca7088..d3c58e27b9 100644 --- a/packages/open-payments/src/config.ts +++ b/packages/open-payments/src/config.ts @@ -1,5 +1,5 @@ export default { OPEN_PAYMENTS_OPEN_API_URL: 'https://raw.githubusercontent.com/interledger/open-payments/7bb2e6a03d7dfe7ecb0553afb6c70741317bb489/openapi/RS/openapi.yaml', - DEFAULT_REQUEST_TIMEOUT: 3_000 + DEFAULT_REQUEST_TIMEOUT_MS: 5_000 } diff --git a/packages/open-payments/src/test/helpers.ts b/packages/open-payments/src/test/helpers.ts index b657490d1d..cb35e7fa62 100644 --- a/packages/open-payments/src/test/helpers.ts +++ b/packages/open-payments/src/test/helpers.ts @@ -1,5 +1,8 @@ import createLogger from 'pino' +import { createAxiosInstance } from '../client/requests' export const silentLogger = createLogger({ level: 'silent' }) + +export const defaultAxiosInstance = createAxiosInstance({ requestTimeoutMs: 0 }) From 05b6cfb6736c95481aa35b4bdc79317d6959f3b5 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Wed, 26 Oct 2022 00:35:45 +0200 Subject: [PATCH 32/33] feat(open-payments): remove unnecessary imports --- packages/open-payments/src/client/index.ts | 2 +- packages/open-payments/src/client/requests.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/open-payments/src/client/index.ts b/packages/open-payments/src/client/index.ts index 306b3d7865..557cb01170 100644 --- a/packages/open-payments/src/client/index.ts +++ b/packages/open-payments/src/client/index.ts @@ -1,5 +1,5 @@ import { createOpenAPI, OpenAPI } from 'openapi' -import createLogger, { Logger, LevelWithSilent as LogLevel } from 'pino' +import createLogger, { Logger } from 'pino' import config from '../config' import { createIncomingPaymentRoutes, diff --git a/packages/open-payments/src/client/requests.ts b/packages/open-payments/src/client/requests.ts index bc7448f3fa..aeb903a636 100644 --- a/packages/open-payments/src/client/requests.ts +++ b/packages/open-payments/src/client/requests.ts @@ -1,4 +1,4 @@ -import axios, { AxiosError, AxiosInstance } from 'axios' +import axios, { AxiosInstance } from 'axios' import { ValidateFunction } from 'openapi' import { ClientDeps } from '.' From f6eb6a8796cbfa380953975767e4bd4a610f45fb Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Wed, 26 Oct 2022 00:49:40 +0200 Subject: [PATCH 33/33] feat(open-payments): update error handling & test --- packages/open-payments/src/client/requests.test.ts | 2 +- packages/open-payments/src/client/requests.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/open-payments/src/client/requests.test.ts b/packages/open-payments/src/client/requests.test.ts index e32128f2d5..29c251916f 100644 --- a/packages/open-payments/src/client/requests.test.ts +++ b/packages/open-payments/src/client/requests.test.ts @@ -85,7 +85,7 @@ describe('requests', (): void => { }, failedValidator ) - ).rejects.toThrow('Failed to validate OpenApi response') + ).rejects.toThrow(/Failed to validate OpenApi response/) }) }) }) diff --git a/packages/open-payments/src/client/requests.ts b/packages/open-payments/src/client/requests.ts index aeb903a636..233cb1354f 100644 --- a/packages/open-payments/src/client/requests.ts +++ b/packages/open-payments/src/client/requests.ts @@ -35,8 +35,10 @@ export const get = async ( return data } catch (error) { - const errorMessage = 'Error when making Open Payments GET request' - logger.error({ errorMessage: error?.message, url }, errorMessage) + const errorMessage = `Error when making Open Payments GET request: ${ + error?.message ? error.message : 'Unknown error' + }` + logger.error({ url }, errorMessage) throw new Error(errorMessage) }