diff --git a/packages/storefront/src/lib/state/modules-info.ts b/packages/storefront/src/lib/state/modules-info.ts index 4c3ac3bac..2b6319da7 100644 --- a/packages/storefront/src/lib/state/modules-info.ts +++ b/packages/storefront/src/lib/state/modules-info.ts @@ -33,8 +33,18 @@ const modulesInfo = reactive<{ available_extra_discount?: ApplyDiscountResponse['available_extra_discount'], }, }>(emptyInfo); +const infoPreset: { [modName: string]: Record } = {}; loadingGlobalInfoPreset.then((modulesInfoPreset) => { - Object.assign(modulesInfo, modulesInfoPreset); + // Copied and gap filled: must not alias (and later empty) + // `window.$storefront.modulesInfoPreset`, nor override persisted info + Object.keys(modulesInfoPreset).forEach((modName) => { + infoPreset[modName] = { ...modulesInfoPreset[modName] }; + Object.keys(infoPreset[modName]).forEach((field) => { + if (modulesInfo[modName][field] === undefined) { + modulesInfo[modName][field] = infoPreset[modName][field]; + } + }); + }); }); const modulesInfoEmitter = mitt(); @@ -63,12 +73,17 @@ export const fetchModule: FetchModule = (modName, reqOptions) => { if (!import.meta.env.SSR) { const storageKey = 'MODULES_INFO'; + const fetchedModules = new Set(); const sessionJson = sessionStorage.getItem(storageKey); if (sessionJson) { try { const persistedValue = JSON.parse(sessionJson); if (persistedValue.__timestamp >= Date.now() - 1000 * 60 * 5) { delete persistedValue.__timestamp; + if (Array.isArray(persistedValue.__fetched)) { + persistedValue.__fetched.forEach((modName) => fetchedModules.add(modName)); + } + delete persistedValue.__fetched; Object.assign(modulesInfo, persistedValue); } else { sessionStorage.removeItem(storageKey); @@ -81,11 +96,18 @@ if (!import.meta.env.SSR) { const fetchInfo = () => { const modulesToFetch: { modName: ModuleApiEndpoint, reqOptions?: any }[] = []; (['list_payments', 'calculate_shipping'] as const).forEach((modName) => { - if (!Object.keys(modulesInfo[modName]).length) { - modulesToFetch.push({ modName }); - } else { + const isInfoSet = Object.keys(modulesInfo[modName]).length > 0; + if (isInfoSet) { modulesInfoEmitter.emit(modName, modulesInfo[modName]); } + // Preset covers `calculate_shipping` (single field), but `list_payments` + // may still miss `loyalty_points_programs` and others + const canSkipRequest = modName === 'calculate_shipping' + ? isInfoSet + : fetchedModules.has(modName); + if (!canSkipRequest) { + modulesToFetch.push({ modName }); + } }); if (Object.keys(utm).length || sessionCoupon) { const { apiContext } = globalThis.$storefront; @@ -120,9 +142,6 @@ if (!import.meta.env.SSR) { fetchModule(modName, reqOptions) .then(async (response) => { if (response.ok) { - Object.keys(modulesInfo[modName]).forEach((key) => { - delete modulesInfo[modName][key]; - }); const modInfo = {}; const { result } = await response.json(); if (Array.isArray(result)) { @@ -184,10 +203,21 @@ if (!import.meta.env.SSR) { } }); } - Object.assign(modulesInfo[modName], modInfo); + // Preset must survive a response missing the field (app error) + const nextInfo = { ...infoPreset[modName], ...modInfo }; + Object.keys(modulesInfo[modName]).forEach((key) => { + if (nextInfo[key] === undefined) { + delete modulesInfo[modName][key]; + } + }); + Object.assign(modulesInfo[modName], nextInfo); + if (Object.keys(modInfo).length) { + fetchedModules.add(modName); + } sessionStorage.setItem(storageKey, JSON.stringify({ ...modulesInfo, __timestamp: Date.now(), + __fetched: [...fetchedModules], })); modulesInfoEmitter.emit(modName, modulesInfo[modName]); }