Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 91
feat: Create new data access to allow for in-memory transactions.#1386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
776bb7dd53c930fc5e6714c41cdfe9d51c7c2ec1c2399217c12631acdee30bda32f05c09641796dffdfdd3d9368a56c1aaaf2afa8186b1638e91696613e6c310ed848638d730d4b611f0900c5391f0cc63dc465c6df24aa7ec11976b3513b8027e490f0fe606172c07ffc7559e45294e915b0a1cf5884d8f50530e2dac6755ec0b48027739c3b08ad11b3121ab44ab47b71180a5d9668422e9e03ccf360157530f487dffeFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -4,10 +4,12 @@ import { RequestLogic } from '@requestnetwork/request-logic'; | ||
| import { TransactionManager } from '@requestnetwork/transaction-manager'; | ||
| import { | ||
| AdvancedLogicTypes, | ||
| ClientTypes, | ||
| CurrencyTypes, | ||
| DataAccessTypes, | ||
| DecryptionProviderTypes, | ||
| EncryptionTypes, | ||
| ExtensionTypes, | ||
| IdentityTypes, | ||
| PaymentTypes, | ||
| RequestLogicTypes, | ||
| @@ -20,6 +22,7 @@ import * as Types from '../types'; | ||
| import ContentDataExtension from './content-data-extension'; | ||
| import Request from './request'; | ||
| import localUtils from './utils'; | ||
| import { NoPersistHttpDataAccess } from '../no-persist-http-data-access'; | ||
| /** | ||
| * Entry point of the request-client.js library. Create requests, get requests, manipulate requests. | ||
| @@ -31,6 +34,7 @@ export default class RequestNetwork { | ||
| private requestLogic: RequestLogicTypes.IRequestLogic; | ||
| private transaction: TransactionTypes.ITransactionManager; | ||
| private advancedLogic: AdvancedLogicTypes.IAdvancedLogic; | ||
| private dataAccess: DataAccessTypes.IDataAccess; | ||
| private contentData: ContentDataExtension; | ||
| private currencyManager: CurrencyTypes.ICurrencyManager; | ||
| @@ -55,6 +59,7 @@ export default class RequestNetwork { | ||
| paymentOptions?: Partial<PaymentNetworkOptions>; | ||
| }) { | ||
| this.currencyManager = currencyManager || CurrencyManager.getDefault(); | ||
| this.dataAccess = dataAccess; | ||
| this.advancedLogic = new AdvancedLogic(this.currencyManager); | ||
| this.transaction = new TransactionManager(dataAccess, decryptionProvider); | ||
| this.requestLogic = new RequestLogic(this.transaction, signatureProvider, this.advancedLogic); | ||
| @@ -85,28 +90,66 @@ export default class RequestNetwork { | ||
| topics, | ||
| ); | ||
| const transactionData = requestLogicCreateResult.meta?.transactionManagerMeta.transactionData; | ||
| const requestId = requestLogicCreateResult.result.requestId; | ||
| const isSkippingPersistence = this.dataAccess instanceof NoPersistHttpDataAccess; | ||
| // create the request object | ||
| const request = new Request( | ||
| requestLogicCreateResult.result.requestId, | ||
| this.requestLogic, | ||
| this.currencyManager, | ||
| { | ||
| contentDataExtension: this.contentData, | ||
| paymentNetwork, | ||
| requestLogicCreateResult, | ||
| skipPaymentDetection: parameters.disablePaymentDetection, | ||
| disableEvents: parameters.disableEvents, | ||
| }, | ||
| ); | ||
| const request = new Request(requestId, this.requestLogic, this.currencyManager, { | ||
| contentDataExtension: this.contentData, | ||
| paymentNetwork, | ||
| requestLogicCreateResult, | ||
| skipPaymentDetection: parameters.disablePaymentDetection, | ||
| disableEvents: parameters.disableEvents, | ||
| // inMemoryInfo is only used when skipPersistence is enabled | ||
| inMemoryInfo: isSkippingPersistence | ||
| ? { | ||
| topics: requestLogicCreateResult.meta.transactionManagerMeta?.topics, | ||
| transactionData: transactionData, | ||
| requestData: this.prepareRequestDataForPayment(transactionData, requestId), | ||
| } | ||
| : null, | ||
| }); | ||
| if (!options?.skipRefresh) { | ||
| if (!options?.skipRefresh && !isSkippingPersistence) { | ||
| // refresh the local request data | ||
| await request.refresh(); | ||
| } | ||
| return request; | ||
| } | ||
| /** | ||
| * Persists an in-memory request to the data-access layer. | ||
| * | ||
| * This method is used to persist requests that were initially created with skipPersistence enabled. | ||
| * | ||
| * @param request The Request object to persist. This must be a request that was created with skipPersistence enabled. | ||
| * @returns A promise that resolves to the result of the persist transaction operation. | ||
| * @throws {Error} If the request's `inMemoryInfo` is not provided, indicating it wasn't created with skipPersistence. | ||
| * @throws {Error} If the current data access instance does not support persistence (e.g., NoPersistHttpDataAccess). | ||
| */ | ||
| public async persistRequest( | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| request: Request, | ||
| ): Promise<DataAccessTypes.IReturnPersistTransaction> { | ||
| if (!request.inMemoryInfo) { | ||
| throw new Error('Cannot persist request without inMemoryInfo.'); | ||
| } | ||
| if (this.dataAccess instanceof NoPersistHttpDataAccess) { | ||
| throw new Error( | ||
| 'Cannot persist request when skipPersistence is enabled. To persist the request, create a new instance of RequestNetwork without skipPersistence being set to true.', | ||
| ); | ||
| } | ||
| const result: DataAccessTypes.IReturnPersistTransaction = | ||
| await this.dataAccess.persistTransaction( | ||
| request.inMemoryInfo.transactionData, | ||
| request.requestId, | ||
| request.inMemoryInfo.topics, | ||
| ); | ||
| return result; | ||
| } | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /** | ||
| * Creates an encrypted request. | ||
| * | ||
| @@ -129,21 +172,27 @@ export default class RequestNetwork { | ||
| topics, | ||
| ); | ||
| const transactionData = requestLogicCreateResult.meta?.transactionManagerMeta.transactionData; | ||
| const requestId = requestLogicCreateResult.result.requestId; | ||
| const isSkippingPersistence = this.dataAccess instanceof NoPersistHttpDataAccess; | ||
| // create the request object | ||
| const request = new Request( | ||
| requestLogicCreateResult.result.requestId, | ||
| this.requestLogic, | ||
| this.currencyManager, | ||
| { | ||
| contentDataExtension: this.contentData, | ||
| paymentNetwork, | ||
| requestLogicCreateResult, | ||
| skipPaymentDetection: parameters.disablePaymentDetection, | ||
| disableEvents: parameters.disableEvents, | ||
| }, | ||
| ); | ||
| const request = new Request(requestId, this.requestLogic, this.currencyManager, { | ||
| contentDataExtension: this.contentData, | ||
| paymentNetwork, | ||
| requestLogicCreateResult, | ||
| skipPaymentDetection: parameters.disablePaymentDetection, | ||
| disableEvents: parameters.disableEvents, | ||
| inMemoryInfo: isSkippingPersistence | ||
| ? { | ||
| topics: requestLogicCreateResult.meta.transactionManagerMeta?.topics, | ||
| transactionData: transactionData, | ||
| requestData: this.prepareRequestDataForPayment(transactionData, requestId), | ||
| } | ||
| : null, | ||
| }); | ||
| if (!options?.skipRefresh) { | ||
| if (!options?.skipRefresh && !isSkippingPersistence) { | ||
| // refresh the local request data | ||
| await request.refresh(); | ||
| } | ||
| @@ -437,4 +486,77 @@ export default class RequestNetwork { | ||
| return { requestParameters: copiedRequestParameters, topics, paymentNetwork }; | ||
| } | ||
| /** | ||
| * Prepares a payment request structure from transaction data. | ||
| * | ||
| * This method is used to create a request structure similar to a persisted request, | ||
| * allowing users to pay before the request is persisted. This is useful in scenarios | ||
| * where a request is created, paid, and then persisted, as opposed to the normal flow | ||
| * of creating, persisting, and then paying the request. | ||
| * | ||
| * @param transactionData The transaction data containing the request information | ||
| * @param requestId The ID of the request | ||
| * @returns The prepared payment request structure or undefined if transaction data is missing | ||
| */ | ||
| private prepareRequestDataForPayment( | ||
| transactionData: DataAccessTypes.ITransaction, | ||
| requestId: string, | ||
| ): ClientTypes.IRequestData { | ||
| const requestData = JSON.parse(transactionData.data as string).data; | ||
| const originalExtensionsData = requestData.parameters.extensionsData; | ||
| const newExtensions: RequestLogicTypes.IExtensionStates = {}; | ||
| for (const extension of originalExtensionsData) { | ||
| if (extension.id !== ExtensionTypes.OTHER_ID.CONTENT_DATA) { | ||
| newExtensions[extension.id] = { | ||
| events: [ | ||
| { | ||
| name: extension.action, | ||
| parameters: { | ||
| paymentAddress: extension.parameters.paymentAddress, | ||
| salt: extension.parameters.salt, | ||
| }, | ||
| timestamp: requestData.parameters.timestamp, | ||
| }, | ||
| ], | ||
| id: extension.id, | ||
| type: ExtensionTypes.TYPE.PAYMENT_NETWORK, | ||
| values: { | ||
| salt: extension.parameters.salt, | ||
| receivedPaymentAmount: '0', | ||
| receivedRefundAmount: '0', | ||
| sentPaymentAmount: '0', | ||
| sentRefundAmount: '0', | ||
| paymentAddress: extension.parameters.paymentAddress, | ||
| }, | ||
| version: extension.version, | ||
| }; | ||
| } | ||
| } | ||
| return { | ||
| requestId: requestId, | ||
| currency: requestData.parameters.currency.type, | ||
| meta: null, | ||
| balance: null, | ||
| expectedAmount: requestData.parameters.expectedAmount, | ||
| contentData: requestData.parameters.extensionsData.find( | ||
| (ext: ExtensionTypes.IAction) => ext.id === ExtensionTypes.OTHER_ID.CONTENT_DATA, | ||
| )?.parameters.content, | ||
| currencyInfo: { | ||
| type: requestData.parameters.currency.type, | ||
| network: requestData.parameters.currency.network, | ||
| value: requestData.parameters.currency.value || '', | ||
| }, | ||
| pending: null, | ||
| extensions: newExtensions, | ||
| extensionsData: requestData.parameters.extensionsData, | ||
| timestamp: requestData.parameters.timestamp, | ||
| version: requestData.parameters.version, | ||
| creator: requestData.parameters.creator, | ||
| state: requestData.parameters.state, | ||
| events: requestData.parameters.events, | ||
| }; | ||
| } | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -11,6 +11,7 @@ import RequestNetwork from './api/request-network'; | ||
| import HttpDataAccess, { NodeConnectionConfig } from './http-data-access'; | ||
| import { MockDataAccess } from '@requestnetwork/data-access'; | ||
| import { MockStorage } from './mock-storage'; | ||
| import { NoPersistHttpDataAccess } from './no-persist-http-data-access'; | ||
| /** | ||
| * Exposes RequestNetwork module configured to use http-data-access. | ||
| @@ -21,10 +22,11 @@ export default class HttpRequestNetwork extends RequestNetwork { | ||
| * | ||
| * @param options.httpConfig Http config that will be used by the underlying data-access. @see ClientTypes.IHttpDataAccessConfig for available options. | ||
| * @param options.nodeConnectionConfig Configuration options to connect to the node. | ||
| * @param options.useMockStorage When true, will use a mock storage in memory. Meant to simplify local development and should never be used in production. | ||
| * @param options.useMockStorage When true, will use a mock storage in memory. Meant to simplify local development and should never be used in production. Overrides `skipPersistence` when both are true. | ||
| * @param options.signatureProvider Module to handle the signature. If not given it will be impossible to create new transaction (it requires to sign). | ||
| * @param options.currencies custom currency list | ||
| * @param options.currencyManager custom currency manager (will override `currencies`) | ||
| * @param options.currencies custom currency list. | ||
| * @param options.currencyManager custom currency manager (will override `currencies`). | ||
| * @param options.skipPersistence allows creating a transaction without immediate persistence. | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| */ | ||
| constructor( | ||
| { | ||
| @@ -35,6 +37,7 @@ export default class HttpRequestNetwork extends RequestNetwork { | ||
| useMockStorage, | ||
| currencyManager, | ||
| paymentOptions, | ||
| skipPersistence, | ||
| }: { | ||
| decryptionProvider?: DecryptionProviderTypes.IDecryptionProvider; | ||
| httpConfig?: Partial<ClientTypes.IHttpDataAccessConfig>; | ||
| @@ -43,13 +46,20 @@ export default class HttpRequestNetwork extends RequestNetwork { | ||
| useMockStorage?: boolean; | ||
| currencyManager?: CurrencyTypes.ICurrencyManager; | ||
| paymentOptions?: Partial<PaymentNetworkOptions>; | ||
| skipPersistence?: boolean; | ||
| } = { | ||
| httpConfig: {}, | ||
| useMockStorage: false, | ||
| skipPersistence: false, | ||
| }, | ||
| ) { | ||
| const dataAccess: DataAccessTypes.IDataAccess = useMockStorage | ||
| ? new MockDataAccess(new MockStorage()) | ||
| : skipPersistence | ||
| ? new NoPersistHttpDataAccess({ | ||
| httpConfig, | ||
| nodeConnectionConfig, | ||
| }) | ||
| : new HttpDataAccess({ httpConfig, nodeConnectionConfig }); | ||
| if (!currencyManager) { | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import HttpDataAccess, { NodeConnectionConfig } from './http-data-access'; | ||
| import { ClientTypes, DataAccessTypes, StorageTypes } from '@requestnetwork/types'; | ||
| import { EventEmitter } from 'events'; | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| export class NoPersistHttpDataAccess extends HttpDataAccess { | ||
| constructor( | ||
| { | ||
| httpConfig, | ||
| nodeConnectionConfig, | ||
| }: { | ||
| httpConfig?: Partial<ClientTypes.IHttpDataAccessConfig>; | ||
| nodeConnectionConfig?: Partial<NodeConnectionConfig>; | ||
| } = { | ||
| httpConfig: {}, | ||
| nodeConnectionConfig: {}, | ||
| }, | ||
| ) { | ||
| super({ httpConfig, nodeConnectionConfig }); | ||
| } | ||
| async persistTransaction( | ||
| transactionData: DataAccessTypes.ITransaction, | ||
| channelId: string, | ||
| topics?: string[], | ||
| ): Promise<DataAccessTypes.IReturnPersistTransaction> { | ||
| const data: DataAccessTypes.IReturnPersistTransactionRaw = { | ||
| meta: { | ||
| topics: topics || [], | ||
| transactionStorageLocation: '', | ||
| storageMeta: { | ||
| state: StorageTypes.ContentState.PENDING, | ||
| timestamp: Date.now() / 1000, | ||
| }, | ||
| }, | ||
| result: {}, | ||
| }; | ||
| const result: DataAccessTypes.IReturnPersistTransaction = Object.assign( | ||
| new EventEmitter() as DataAccessTypes.PersistTransactionEmitter, | ||
| data, | ||
| ); | ||
| // Emit confirmation instantly since data is not going to be persisted | ||
| result.emit('confirmed', result); | ||
| return result; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -18,7 +18,7 @@ afterAll(() => { | ||
| }); | ||
| afterEach(() => { | ||
| mockServer.restoreHandlers(); | ||
| mockServer.resetHandlers(); | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| }); | ||
| describe('HttpRequestNetwork', () => { | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.