diff --git a/machines/issuersMachine.ts b/machines/issuersMachine.ts index b85b622d..1ddf71b1 100644 --- a/machines/issuersMachine.ts +++ b/machines/issuersMachine.ts @@ -20,6 +20,7 @@ import { CredentialWrapper, VerifiableCredential, } from '../types/VC/EsignetMosipVC/vc'; +import {CACHED_API} from '../shared/api'; const model = createModel( { @@ -360,21 +361,13 @@ export const IssuersMachine = model.createMachine( }, services: { downloadIssuersList: async () => { - const defaultIssuer = { - id: 'UIN, VID, AID', - displayName: 'UIN, VID, AID', - }; + return await CACHED_API.fetchIssuers(); + }, - const response = await request('GET', '/residentmobileapp/issuers'); - return [defaultIssuer, ...response.response.issuers]; - }, downloadIssuerConfig: async (_, event) => { - const response = await request( - 'GET', - `/residentmobileapp/issuers/${event.id}`, - ); - return response.response; + return await CACHED_API.fetchIssuerConfig(event.id); }, + downloadCredential: async context => { const body = await getBody(context); const response = await fetch( diff --git a/shared/InitialConfig.ts b/shared/InitialConfig.ts new file mode 100644 index 00000000..44b37075 --- /dev/null +++ b/shared/InitialConfig.ts @@ -0,0 +1,21 @@ +// Initial configs used by app if network is not available when app is opened for first time. once network is available response is cached and used from then +// Note: Need to keep this config in sync with actual mimoto response. + +export const INITIAL_CONFIG = { + // These properties are stored in mosip-config github repo / inji-default.properties + allProperties: { + modelDownloadMaxRetry: '10', + audience: 'ida-binding', + allowedInternalAuthType: 'otp,bio-Finger,bio-Iris,bio-Face', + vcDownloadMaxRetry: '10', + minStorageRequiredForAuditEntry: '2', + minStorageRequired: '2', + vcDownloadPoolInterval: '6000', + issuer: 'residentapp', + allowedAuthType: 'demo,otp,bio-Finger,bio-Iris,bio-Face', + allowedEkycAuthType: 'demo,otp,bio-Finger,bio-Iris,bio-Face', + warningDomainName: '', + aboutInjiUrl: 'https://docs.mosip.io/inji', + faceSdkModelUrl: '', + }, +}; diff --git a/shared/api.ts b/shared/api.ts new file mode 100644 index 00000000..0a25d56a --- /dev/null +++ b/shared/api.ts @@ -0,0 +1,168 @@ +import {request} from './request'; +import Storage, {API_CACHED_STORAGE_KEYS} from './storage'; +import {COMMON_PROPS_KEY} from './commonprops/commonProps'; +import {INITIAL_CONFIG} from './InitialConfig'; + +export const API_URLS = { + issuersList: { + method: 'GET', + buildURL: (): `/${string}` => '/residentmobileapp/issuers', + }, + issuerConfig: { + method: 'GET', + buildURL: (issuerId: string): `/${string}` => + `/residentmobileapp/issuers/${issuerId}`, + }, + allProperties: { + method: 'GET', + buildURL: (): `/${string}` => '/residentmobileapp/allProperties', + }, +}; + +export const API = { + fetchIssuers: async () => { + const defaultIssuer = { + id: 'UIN, VID, AID', + displayName: 'UIN, VID, AID', + }; + + const response = await request( + API_URLS.issuersList.method, + API_URLS.issuersList.buildURL(), + ); + return [defaultIssuer, ...(response.response.issuers || [])]; + }, + + fetchIssuerConfig: async (issuerId: string) => { + const response = await request( + API_URLS.issuerConfig.method, + API_URLS.issuerConfig.buildURL(issuerId), + ); + return response.response; + }, + + fetchAllProperties: async () => { + const response = await request( + API_URLS.allProperties.method, + API_URLS.allProperties.buildURL(), + ); + return response.response; + }, +}; + +export const CACHED_API = { + fetchIssuers: () => + generateCacheAPIFunction({ + cacheKey: API_CACHED_STORAGE_KEYS.fetchIssuers, + fetchCall: API.fetchIssuers, + }), + + fetchIssuerConfig: (issuerId: string) => + generateCacheAPIFunction({ + cacheKey: API_CACHED_STORAGE_KEYS.fetchIssuerConfig(issuerId), + fetchCall: API.fetchIssuerConfig.bind(null, issuerId), + }), + + getAllProperties: () => + generateCacheAPIFunction({ + isCachePreferred: true, + cacheKey: COMMON_PROPS_KEY, + fetchCall: API.fetchAllProperties, + onErrorHardCodedValue: INITIAL_CONFIG.allProperties, + }), +}; + +interface GenerateCacheAPIFunctionProps { + isCachePreferred?: boolean; + cacheKey: string; + fetchCall: (...props: any) => Promise; + onErrorHardCodedValue?: any; +} + +async function generateCacheAPIFunction({ + isCachePreferred = false, + cacheKey = '', + fetchCall = () => Promise.resolve(), + onErrorHardCodedValue = undefined, +}: GenerateCacheAPIFunctionProps) { + if (isCachePreferred) { + return await generateCacheAPIFunctionWithCachePreference( + cacheKey, + fetchCall, + onErrorHardCodedValue, + ); + } else { + return await generateCacheAPIFunctionWithAPIPreference( + cacheKey, + fetchCall, + onErrorHardCodedValue, + ); + } +} + +async function generateCacheAPIFunctionWithCachePreference( + cacheKey: string, + fetchCall: (...props: any[]) => any, + onErrorHardCodedValue?: any, +) { + try { + const response = (await Storage.getItem(cacheKey)) as string; + + if (response) { + return JSON.parse(response); + } else { + const response = await fetchCall(); + + Storage.setItem(cacheKey, JSON.stringify(response)).then(() => + console.log('Cached response for ' + cacheKey), + ); + + return response; + } + } catch (error) { + console.warn(`Failed to load due to network issue in cache preferred api call. + cache key:${cacheKey} and has onErrorHardCodedValue:${ + onErrorHardCodedValue != undefined + }`); + console.log(error); + + if (onErrorHardCodedValue != undefined) { + return onErrorHardCodedValue; + } else { + throw error; + } + } +} + +async function generateCacheAPIFunctionWithAPIPreference( + cacheKey: string, + fetchCall: (...props: any[]) => any, + onErrorHardCodedValue?: any, +) { + try { + const response = await fetchCall(); + Storage.setItem(cacheKey, JSON.stringify(response)).then(() => + console.log('Cached response for ' + cacheKey), + ); + return response; + } catch (error) { + console.warn(`Failed to load due to network issue in API preferred api call. + cache key:${cacheKey} and has onErrorHardCodedValue:${ + onErrorHardCodedValue != undefined + }`); + + console.log(error); + + const response = (await Storage.getItem(cacheKey)) as string; + + if (response) { + return JSON.parse(response); + } else { + if (onErrorHardCodedValue != undefined) { + return onErrorHardCodedValue; + } else { + throw error; + } + } + } +} diff --git a/shared/commonprops/commonProps.ts b/shared/commonprops/commonProps.ts index fa312b64..1a48ae07 100644 --- a/shared/commonprops/commonProps.ts +++ b/shared/commonprops/commonProps.ts @@ -1,28 +1,13 @@ -import { request } from '../request'; -import Storage from '../storage'; -import { init } from 'mosip-inji-face-sdk'; -import { changeCrendetialRegistry } from '../constants'; +import {init} from 'mosip-inji-face-sdk'; +import {changeCrendetialRegistry} from '../constants'; +import {CACHED_API} from '../api'; export const COMMON_PROPS_KEY: string = 'CommonPropsKey-' + '6964d04a-9268-11ed-a1eb-0242ac120002'; export default async function getAllConfigurations(host = undefined) { - try { - host && changeCrendetialRegistry(host); - const response = await Storage.getItem(COMMON_PROPS_KEY); - if (response) { - return JSON.parse(response); - } else { - const resp = await request('GET', '/residentmobileapp/allProperties'); - const injiProps = resp.response; - const injiPropsString = JSON.stringify(injiProps); - await Storage.setItem(COMMON_PROPS_KEY, injiPropsString); - return injiProps; - } - } catch (error) { - console.log(error); - throw error; - } + host && changeCrendetialRegistry(host); + return await CACHED_API.getAllProperties(); } export async function downloadModel() { diff --git a/shared/request.ts b/shared/request.ts index 9df064b3..16a0837c 100644 --- a/shared/request.ts +++ b/shared/request.ts @@ -5,6 +5,8 @@ import { import {__AppId} from './GlobalVariables'; import {HOST, MIMOTO_BASE_URL} from './constants'; +export type HTTP_METHOD = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; + export class BackendResponseError extends Error { constructor(name: string, message: string) { super(message); @@ -13,7 +15,7 @@ export class BackendResponseError extends Error { } export async function request( - method: 'GET' | 'POST' | 'PATCH', + method: HTTP_METHOD, path: `/${string}`, body?: Record, host = MIMOTO_BASE_URL, diff --git a/shared/storage.ts b/shared/storage.ts index 923edb65..6a406ebc 100644 --- a/shared/storage.ts +++ b/shared/storage.ts @@ -27,6 +27,12 @@ import {VCMetadata} from './VCMetadata'; const MMKV = new MMKVLoader().initialize(); const vcDirectoryPath = `${DocumentDirectoryPath}/inji/VC`; +export const API_CACHED_STORAGE_KEYS = { + fetchIssuers: 'CACHE_FETCH_ISSUERS', + fetchIssuerConfig: (issuerId: string) => + `CACHE_FETCH_ISSUER_CONFIG_${issuerId}`, +}; + async function generateHmac( encryptionKey: string, data: string,