From b214ba7c14329767a00700b4f2368ae20d5b01a0 Mon Sep 17 00:00:00 2001 From: Mark Paul Date: Mon, 11 Nov 2024 14:27:38 +1100 Subject: [PATCH] feature: use ITH_GLOBAL_* if available, introduce poc for Simple Caching Module --- package.json | 2 +- src/common/utils.ts | 47 +++++++++++++++++++++++++++++++++++++++ src/config.ts | 44 +++++++++++++++++++++++++++++++----- src/datanft.ts | 35 ++++++++++++++++++++++------- tests/environment.test.ts | 4 ++++ 5 files changed, 117 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 10c064d..4d3c030 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@itheum/sdk-mx-data-nft", - "version": "3.8.0-alpha.2", + "version": "3.8.0-alpha.3", "description": "SDK for Itheum's Data NFT Technology on MultiversX Blockchain", "main": "out/index.js", "types": "out/index.d.js", diff --git a/src/common/utils.ts b/src/common/utils.ts index e82a0b2..e62d1c7 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -600,3 +600,50 @@ export function checkStatus(response: Response) { throw new ErrFetch(response.status, response.statusText); } } + +/* +Simple Caching Module helps throttle frequently used calls to RPC that fetch data +this helps speed up the client side app and also reduces calls to the RPC +we allow consumer to set a custom TTL in MS for how long data is stored in cache +*/ +const sessionCache: Record = {}; + +export function getDataFromClientSessionCache(cacheKey: string) { + const cacheObject = sessionCache[cacheKey]; + + if (!cacheObject) { + console.log('getDataFromClientSessionCache: not found'); + return false; + } else { + // did it expire? is so, delete it from the cache + if (cacheObject.addedOn - Date.now() > cacheObject.expireAfter) { + console.log('getDataFromClientSessionCache: expired'); + delete sessionCache[cacheKey]; // remove it from cache as its expired + return false; + } else { + console.log('getDataFromClientSessionCache: available'); + return cacheObject.payload; + } + } +} + +export function setDataToClientSessionCache( + cacheKey: string, + jsonData: any, + ttlInMs?: number +) { + const howManyMsToCacheFor = ttlInMs || 120000; // 120000 is 2 min default TTL + + sessionCache[cacheKey] = { + payload: jsonData, + addedOn: Date.now(), + expireAfter: howManyMsToCacheFor + }; + + console.log( + 'setDataToClientSessionCache: cached for ms ', + howManyMsToCacheFor + ); + + return true; +} diff --git a/src/config.ts b/src/config.ts index 91ec1a0..e732939 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,12 @@ +/* +Host apps like the data dex or explorer, when the MVX dappProvider in initialized (very early in the page) +it calls a utility method that loads the best RPC to use in the ITH_GLOBAL_MVX_RPC_API_SESSION global variable +so we aim to use that if available so this SDK uses the same RPC as the host to talk to the MVX chain +*/ +declare const window: { + ITH_GLOBAL_MVX_RPC_API_SESSION: string; +} & Window; + export enum EnvironmentsEnum { devnet = 'devnet', testnet = 'testnet', @@ -9,19 +18,31 @@ export interface Config { networkProvider: string; } +// note that in all rpc check methods below we check if window === 'undefined' as this is need for tests to pass const devnetNetworkConfig: Config = { chainID: 'D', - networkProvider: 'https://devnet-api.multiversx.com' + networkProvider: + typeof window === 'undefined' + ? 'https://devnet-api.multiversx.com' + : window.ITH_GLOBAL_MVX_RPC_API_SESSION || + 'https://devnet-api.multiversx.com' }; const mainnetNetworkConfig: Config = { chainID: '1', - networkProvider: 'https://api.multiversx.com' + networkProvider: + typeof window === 'undefined' + ? 'https://api.multiversx.com' + : window.ITH_GLOBAL_MVX_RPC_API_SESSION || 'https://api.multiversx.com' }; const testnetNetworkConfig: Config = { chainID: 'T', - networkProvider: 'https://testnet-api.multiversx.com' + networkProvider: + typeof window === 'undefined' + ? 'https://testnet-api.multiversx.com' + : window.ITH_GLOBAL_MVX_RPC_API_SESSION || + 'https://testnet-api.multiversx.com' }; export const itheumTokenIdentifier: { [key in EnvironmentsEnum]: string } = { @@ -64,9 +85,20 @@ export const livelinessStakeContractAddress: { }; export const apiConfiguration: { [key in EnvironmentsEnum]: string } = { - devnet: 'https://devnet-api.multiversx.com', - mainnet: 'https://api.multiversx.com', - testnet: 'https://testnet-api.multiversx.com' + devnet: + typeof window === 'undefined' + ? 'https://devnet-api.multiversx.com' + : window.ITH_GLOBAL_MVX_RPC_API_SESSION || + 'https://devnet-api.multiversx.com', + mainnet: + typeof window === 'undefined' + ? 'https://api.multiversx.com' + : window.ITH_GLOBAL_MVX_RPC_API_SESSION || 'https://api.multiversx.com', + testnet: + typeof window === 'undefined' + ? 'https://testnet-api.multiversx.com' + : window.ITH_GLOBAL_MVX_RPC_API_SESSION || + 'https://testnet-api.multiversx.com' }; export const networkConfiguration: { [key in EnvironmentsEnum]: Config } = { diff --git a/src/datanft.ts b/src/datanft.ts index e9d76f3..37acc54 100644 --- a/src/datanft.ts +++ b/src/datanft.ts @@ -10,7 +10,9 @@ import { numberToPaddedHex, overrideMarshalUrl, parseDataNft, - validateSpecificParamsViewData + validateSpecificParamsViewData, + getDataFromClientSessionCache, + setDataToClientSessionCache } from './common/utils'; import { Config, @@ -163,15 +165,32 @@ export class DataNft implements DataNftType { if (identifiers.length > MAX_ITEMS) { throw new ErrTooManyItems(); } - const response = await fetch( - `${this.apiConfiguration}/nfts?identifiers=${identifiers.join( - ',' - )}&withSupply=true&size=${identifiers.length}` - ); - checkStatus(response); + // lets not make the call if not needed + if (identifiers.length === 0) { + return []; + } + + const fetchUrl = `${ + this.apiConfiguration + }/nfts?identifiers=${identifiers.join(',')}&withSupply=true&size=${ + identifiers.length + }`; + + // check if its in session cache + let jsonDataPayload = null; + const getFromSessionCache = getDataFromClientSessionCache(fetchUrl); + + if (!getFromSessionCache) { + const response = await fetch(fetchUrl); + checkStatus(response); + jsonDataPayload = await response.json(); + setDataToClientSessionCache(fetchUrl, jsonDataPayload, 5 * 60 * 1000); + } else { + jsonDataPayload = getFromSessionCache; + } - const data: NftType[] = await response.json(); + const data: NftType[] = jsonDataPayload; try { const dataNfts = data.map((value) => parseDataNft(value)); diff --git a/tests/environment.test.ts b/tests/environment.test.ts index 40de54b..320bf71 100644 --- a/tests/environment.test.ts +++ b/tests/environment.test.ts @@ -1,3 +1,7 @@ +declare const window: { + ITH_GLOBAL_MVX_RPC_API_SESSION: string; +} & Window; + import { ApiNetworkProvider } from '@multiversx/sdk-core/out'; import { DataNftMarket, SftMinter } from '../src/index';