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 {
|
try {
|
||||||
const embeddedHash = 'embedded';
|
const embeddedHash = 'embedded';
|
||||||
storeGedcom(embeddedHash, gedcom, new Map());
|
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);
|
const software = getSoftware(data.gedcom.head);
|
||||||
analyticsEvent('embedded_file_loaded', {
|
analyticsEvent('embedded_file_loaded', {
|
||||||
event_label: software,
|
event_label: software,
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ async function prepareData(
|
|||||||
cacheId: string,
|
cacheId: string,
|
||||||
images?: Map<string, string>,
|
images?: Map<string, string>,
|
||||||
onProgress?: (status: string) => void,
|
onProgress?: (status: string) => void,
|
||||||
|
useSessionCache = true,
|
||||||
): Promise<TopolaData> {
|
): Promise<TopolaData> {
|
||||||
const data = await convertGedcom(gedcom, images || new Map(), onProgress);
|
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) {
|
if (gedcom.length <= SESSION_CACHE_GEDCOM_LIMIT) {
|
||||||
const dataToSerialize = {...data};
|
const dataToSerialize = {...data};
|
||||||
delete dataToSerialize.images;
|
delete dataToSerialize.images;
|
||||||
@@ -184,14 +185,18 @@ export async function loadFromUrl(
|
|||||||
export async function loadGedcom(
|
export async function loadGedcom(
|
||||||
hash: string,
|
hash: string,
|
||||||
onProgress?: (status: string) => void,
|
onProgress?: (status: string) => void,
|
||||||
|
options?: {useSessionCache?: boolean},
|
||||||
): Promise<TopolaData> {
|
): Promise<TopolaData> {
|
||||||
try {
|
const useSessionCache = options?.useSessionCache ?? true;
|
||||||
const cachedData = sessionStorage.getItem(hash);
|
if (useSessionCache) {
|
||||||
if (cachedData) {
|
try {
|
||||||
return JSON.parse(cachedData);
|
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
|
// Retrieve from the in-memory store (survives within-tab navigation even
|
||||||
// when the GEDCOM is too large for history.pushState or sessionStorage).
|
// when the GEDCOM is too large for history.pushState or sessionStorage).
|
||||||
@@ -203,7 +208,13 @@ export async function loadGedcom(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await prepareData(stored.gedcom, hash, stored.images, onProgress);
|
return await prepareData(
|
||||||
|
stored.gedcom,
|
||||||
|
hash,
|
||||||
|
stored.images,
|
||||||
|
onProgress,
|
||||||
|
useSessionCache,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
revokeObjectUrls(stored.images);
|
revokeObjectUrls(stored.images);
|
||||||
throw error;
|
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 {storeGedcom} from './gedcom_store';
|
||||||
import {loadGedcom} from './load_data';
|
import {loadGedcom} from './load_data';
|
||||||
|
import {mockSessionStorage} from './test_helpers';
|
||||||
|
|
||||||
// Minimal GEDCOM with 1 individual and 1 family so convertGedcom succeeds.
|
// Minimal GEDCOM with 1 individual and 1 family so convertGedcom succeeds.
|
||||||
const MINIMAL_GEDCOM = [
|
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