mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-07-17 17:21:48 +00:00
Don't cache embedded GEDCOM in sessionStorage (#308)
This commit is contained in:
@@ -61,7 +61,15 @@ export class EmbeddedDataSource implements DataSource<EmbeddedSourceSpec> {
|
||||
try {
|
||||
const embeddedHash = 'embedded';
|
||||
storeGedcom(embeddedHash, gedcom, new Map());
|
||||
const data = await loadGedcom(embeddedHash, onProgress);
|
||||
// Embedded data is volatile: the parent re-posts a fresh GEDCOM on every
|
||||
// load (and a page reload re-runs the ready/parent_ready handshake, so
|
||||
// the parent re-sends). That GEDCOM may inline object URLs (blob:/data:)
|
||||
// that die with the previous document. Persisting it to sessionStorage
|
||||
// under the constant 'embedded' key would return dead URLs on the next
|
||||
// load, so opt out of the session cache for embedded data.
|
||||
const data = await loadGedcom(embeddedHash, onProgress, {
|
||||
useSessionCache: false,
|
||||
});
|
||||
const software = getSoftware(data.gedcom.head);
|
||||
analyticsEvent('embedded_file_loaded', {
|
||||
event_label: software,
|
||||
|
||||
@@ -35,9 +35,10 @@ async function prepareData(
|
||||
cacheId: string,
|
||||
images?: Map<string, string>,
|
||||
onProgress?: (status: string) => void,
|
||||
useSessionCache = true,
|
||||
): Promise<TopolaData> {
|
||||
const data = await convertGedcom(gedcom, images || new Map(), onProgress);
|
||||
if (!images || images.size === 0) {
|
||||
if (useSessionCache && (!images || images.size === 0)) {
|
||||
if (gedcom.length <= SESSION_CACHE_GEDCOM_LIMIT) {
|
||||
const dataToSerialize = {...data};
|
||||
delete dataToSerialize.images;
|
||||
@@ -184,14 +185,18 @@ export async function loadFromUrl(
|
||||
export async function loadGedcom(
|
||||
hash: string,
|
||||
onProgress?: (status: string) => void,
|
||||
options?: {useSessionCache?: boolean},
|
||||
): Promise<TopolaData> {
|
||||
try {
|
||||
const cachedData = sessionStorage.getItem(hash);
|
||||
if (cachedData) {
|
||||
return JSON.parse(cachedData);
|
||||
const useSessionCache = options?.useSessionCache ?? true;
|
||||
if (useSessionCache) {
|
||||
try {
|
||||
const cachedData = sessionStorage.getItem(hash);
|
||||
if (cachedData) {
|
||||
return JSON.parse(cachedData);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load data from session storage: ' + e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load data from session storage: ' + e);
|
||||
}
|
||||
// Retrieve from the in-memory store (survives within-tab navigation even
|
||||
// when the GEDCOM is too large for history.pushState or sessionStorage).
|
||||
@@ -203,7 +208,13 @@ export async function loadGedcom(
|
||||
);
|
||||
}
|
||||
try {
|
||||
return await prepareData(stored.gedcom, hash, stored.images, onProgress);
|
||||
return await prepareData(
|
||||
stored.gedcom,
|
||||
hash,
|
||||
stored.images,
|
||||
onProgress,
|
||||
useSessionCache,
|
||||
);
|
||||
} catch (error) {
|
||||
revokeObjectUrls(stored.images);
|
||||
throw error;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {describe, expect, it} from '@jest/globals';
|
||||
import {beforeEach, describe, expect, it} from '@jest/globals';
|
||||
import {storeGedcom} from './gedcom_store';
|
||||
import {loadGedcom} from './load_data';
|
||||
import {mockSessionStorage} from './test_helpers';
|
||||
|
||||
// Minimal GEDCOM with 1 individual and 1 family so convertGedcom succeeds.
|
||||
const MINIMAL_GEDCOM = [
|
||||
@@ -43,3 +44,31 @@ describe('loadGedcom()', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadGedcom() session cache', () => {
|
||||
let sessionStorageMock: {[key: string]: string};
|
||||
|
||||
beforeEach(() => {
|
||||
sessionStorageMock = mockSessionStorage();
|
||||
});
|
||||
|
||||
it('caches prepared data in sessionStorage by default', async () => {
|
||||
const hash = 'cache-default-hash';
|
||||
storeGedcom(hash, MINIMAL_GEDCOM, new Map());
|
||||
await loadGedcom(hash);
|
||||
expect(sessionStorageMock[hash]).toBeDefined();
|
||||
});
|
||||
|
||||
it('ignores and preserves the cache when useSessionCache is false', async () => {
|
||||
const hash = 'cache-disabled-hash';
|
||||
sessionStorageMock[hash] = JSON.stringify({stale: true});
|
||||
storeGedcom(hash, MINIMAL_GEDCOM, new Map());
|
||||
|
||||
const data = await loadGedcom(hash, undefined, {useSessionCache: false});
|
||||
|
||||
// Returned from the in-memory store, not the stale cache.
|
||||
expect(data.chartData.indis).toHaveLength(1);
|
||||
// The stale entry is neither served nor overwritten with fresh data.
|
||||
expect(sessionStorageMock[hash]).toBe(JSON.stringify({stale: true}));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user