fix(offline): surface degraded/missing preloads, join re-entrant runs, cache manifest to IDB
- loadManifest() goes through DataCache.fetchWithFallback: the file list is written to IDB and survives SW-cache eviction; a run with the reduced fallback set is flagged and shown as degraded (new i18n en/ru/cu). - 404 answers are counted as 'missing' (deployment drift) with a preload-missing diag entry, instead of silently reporting Done. - preload() is re-entrancy safe: concurrent calls join the in-flight run. - versions.json is loaded once per session (was fetched per translation). - Write-through failures in fetchWithFallback()/fetch() log idb-write-fail. - preload-manifest.json SW route is NetworkFirst (fresh coverage online). - Manifest now also covers the shared/lives + shared/commands fallbacks.
This commit is contained in:
1 parent
1201e16d5e
commit
8c442bf7b6
7 files changed
+173
-44
No files matched your search
@@ -48,6 +48,21 @@ function calendarFiles(lang: string): string[] {
|
|||||||
if (exists(f)) files.push(`/data${f}`);
|
if (exists(f)) files.push(`/data${f}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared fallback lives/commands — normally served per-language, but the
|
||||||
|
// runtime chain ends with a 'shared' attempt; keep those copies offline too.
|
||||||
|
for (const sub of ['lives', 'commands']) {
|
||||||
|
const subDir = path.join(DATA_DIR, 'shared', sub);
|
||||||
|
if (!fs.existsSync(subDir)) continue;
|
||||||
|
const walkShared = (d: string, rel: string): void => {
|
||||||
|
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
||||||
|
const p = path.join(d, e.name);
|
||||||
|
if (e.isDirectory()) walkShared(p, `${rel}/${e.name}`);
|
||||||
|
else if (e.name.endsWith('.json')) files.push(`/data/shared/${sub}${rel}/${e.name}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walkShared(subDir, '');
|
||||||
|
}
|
||||||
|
|
||||||
// Menaion bundle
|
// Menaion bundle
|
||||||
const mb = `/${lang}/menaion-bundle.json`;
|
const mb = `/${lang}/menaion-bundle.json`;
|
||||||
if (exists(mb)) files.push(`/data${mb}`);
|
if (exists(mb)) files.push(`/data${mb}`);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
* const estimate = await DataCache.estimate();
|
* const estimate = await DataCache.estimate();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { diagLog } from './diag-log';
|
||||||
|
|
||||||
const DB_NAME = 'ponomar-cache';
|
const DB_NAME = 'ponomar-cache';
|
||||||
const DB_VERSION = 1;
|
const DB_VERSION = 1;
|
||||||
const STORE_NAME = 'data';
|
const STORE_NAME = 'data';
|
||||||
@@ -220,7 +222,11 @@ export class DataCache {
|
|||||||
const ct = resp.headers.get('content-type');
|
const ct = resp.headers.get('content-type');
|
||||||
if (resp.ok && !(ct && ct.includes('text/html'))) {
|
if (resp.ok && !(ct && ct.includes('text/html'))) {
|
||||||
const data: unknown = type === 'text' ? await resp.text() : await resp.json();
|
const data: unknown = type === 'text' ? await resp.text() : await resp.json();
|
||||||
await DataCache.set(`url:${url}`, data);
|
const stored = await DataCache.set(`url:${url}`, data);
|
||||||
|
if (!stored) {
|
||||||
|
// Online view still gets its data; the offline copy is missing.
|
||||||
|
diagLog('idb-write-fail', { url });
|
||||||
|
}
|
||||||
return data as T;
|
return data as T;
|
||||||
}
|
}
|
||||||
} catch { /* offline or invalid response — fall through to IDB */ }
|
} catch { /* offline or invalid response — fall through to IDB */ }
|
||||||
@@ -261,7 +267,8 @@ export class DataCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache the fetched data
|
// Cache the fetched data
|
||||||
await DataCache.set(cacheKey, data, options?.ttl);
|
const stored = await DataCache.set(cacheKey, data, options?.ttl);
|
||||||
|
if (!stored) diagLog('idb-write-fail', { url });
|
||||||
return data as T;
|
return data as T;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -524,6 +524,8 @@ export interface Translations {
|
|||||||
offlineCalculating: string;
|
offlineCalculating: string;
|
||||||
offlineDone: string;
|
offlineDone: string;
|
||||||
offlineFailed: string;
|
offlineFailed: string;
|
||||||
|
offlineDegraded: string;
|
||||||
|
offlineMissing: string;
|
||||||
offlineSelectLang: string;
|
offlineSelectLang: string;
|
||||||
offlineSelectAll: string;
|
offlineSelectAll: string;
|
||||||
offlineCleared: string;
|
offlineCleared: string;
|
||||||
@@ -1097,6 +1099,8 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineCalculating: 'Calculating storage...',
|
offlineCalculating: 'Calculating storage...',
|
||||||
offlineDone: 'Done! Data cached for offline use.',
|
offlineDone: 'Done! Data cached for offline use.',
|
||||||
offlineFailed: 'files failed to load (retry to attempt again)',
|
offlineFailed: 'files failed to load (retry to attempt again)',
|
||||||
|
offlineDegraded: 'Preload could not read the full file list — a reduced set was cached. Check your connection and try again.',
|
||||||
|
offlineMissing: 'Note: {0} files were not found on the server.',
|
||||||
offlineSelectLang: 'Please select at least one language.',
|
offlineSelectLang: 'Please select at least one language.',
|
||||||
offlineSelectAll: 'Select all',
|
offlineSelectAll: 'Select all',
|
||||||
offlineCleared: 'Cache cleared.',
|
offlineCleared: 'Cache cleared.',
|
||||||
@@ -1668,6 +1672,8 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineCalculating: 'Расчёт размера...',
|
offlineCalculating: 'Расчёт размера...',
|
||||||
offlineDone: 'Готово! Данные закэшированы для офлайн-использования.',
|
offlineDone: 'Готово! Данные закэшированы для офлайн-использования.',
|
||||||
offlineFailed: 'файлов не загружено (повторите для повторной попытки)',
|
offlineFailed: 'файлов не загружено (повторите для повторной попытки)',
|
||||||
|
offlineDegraded: 'Не удалось загрузить полный список файлов — закэширован сокращённый набор. Проверьте соединение и повторите.',
|
||||||
|
offlineMissing: 'Примечание: {0} файлов не найдено на сервере.',
|
||||||
offlineSelectLang: 'Пожалуйста, выберите хотя бы один язык.',
|
offlineSelectLang: 'Пожалуйста, выберите хотя бы один язык.',
|
||||||
offlineSelectAll: 'Выбрать все',
|
offlineSelectAll: 'Выбрать все',
|
||||||
offlineCleared: 'Кэш очищен.',
|
offlineCleared: 'Кэш очищен.',
|
||||||
@@ -2239,6 +2245,8 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineCalculating: 'Расчётъ размѣра...',
|
offlineCalculating: 'Расчётъ размѣра...',
|
||||||
offlineDone: 'Готово! Данныя закешированы для офлайн-потребленїя.',
|
offlineDone: 'Готово! Данныя закешированы для офлайн-потребленїя.',
|
||||||
offlineFailed: 'фа́йловъ не загружено (повтори́те для повторной попытки)',
|
offlineFailed: 'фа́йловъ не загружено (повтори́те для повторной попытки)',
|
||||||
|
offlineDegraded: 'Не возмо́жно бѣ̀ прочестѝ по́лный спи́сокъ фа́йловъ — закеши́рованъ сокраще́нный набо́ръ. Провѣ́рьте соедине́нїе и҆ повтори́те.',
|
||||||
|
offlineMissing: 'Примѣча́нїе: {0} фа́йловъ не ѡ҆брѣте́но на се́рверѣ.',
|
||||||
offlineSelectLang: 'Пожалуйста, изберите хотя бы единъ языкъ.',
|
offlineSelectLang: 'Пожалуйста, изберите хотя бы единъ языкъ.',
|
||||||
offlineSelectAll: 'И҆збра́ти всѧ̑',
|
offlineSelectAll: 'И҆збра́ти всѧ̑',
|
||||||
offlineCleared: 'Кешъ очищенъ.',
|
offlineCleared: 'Кешъ очищенъ.',
|
||||||
|
|||||||
+53
-21
@@ -30,6 +30,10 @@ export interface PreloadProgress {
|
|||||||
done: boolean;
|
done: boolean;
|
||||||
failed: number;
|
failed: number;
|
||||||
failedFiles?: string[];
|
failedFiles?: string[];
|
||||||
|
/** Files the server answered 404 for (deployment drift). */
|
||||||
|
missing?: number;
|
||||||
|
/** True when the manifest was unavailable and a reduced fallback set ran. */
|
||||||
|
manifestMissing?: boolean;
|
||||||
aborted?: boolean;
|
aborted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,19 +58,17 @@ let manifestPromise: Promise<Record<string, Record<string, string[]>> | null> |
|
|||||||
|
|
||||||
function loadManifest(): Promise<Record<string, Record<string, string[]>> | null> {
|
function loadManifest(): Promise<Record<string, Record<string, string[]>> | null> {
|
||||||
if (!manifestPromise) {
|
if (!manifestPromise) {
|
||||||
manifestPromise = (async () => {
|
// Network-first with the IDB fallback: a copy written while online
|
||||||
try {
|
// survives SW-cache eviction, so a later (possibly offline) call can
|
||||||
const resp = await fetch('/data/shared/preload-manifest.json');
|
// still read the full file list instead of the reduced fallback set.
|
||||||
if (!resp.ok) return null;
|
manifestPromise = DataCache
|
||||||
return await resp.json();
|
.fetchWithFallback<Record<string, Record<string, string[]>>>('/data/shared/preload-manifest.json')
|
||||||
} catch {
|
.then((m) => {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})().then((m) => {
|
|
||||||
// Don't cache a failure forever — allow retry on next call
|
// Don't cache a failure forever — allow retry on next call
|
||||||
if (m === null) {
|
if (m === null || typeof m !== 'object') {
|
||||||
diagLog('manifest-unavailable');
|
diagLog('manifest-unavailable');
|
||||||
manifestPromise = null;
|
manifestPromise = null;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return m;
|
return m;
|
||||||
});
|
});
|
||||||
@@ -74,6 +76,24 @@ function loadManifest(): Promise<Record<string, Record<string, string[]>> | null
|
|||||||
return manifestPromise;
|
return manifestPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** versions.json loaded once per session (retried on failure). */
|
||||||
|
let bibleVersionsPromise: Promise<any[] | null> | null = null;
|
||||||
|
|
||||||
|
function loadBibleVersions(): Promise<any[] | null> {
|
||||||
|
if (!bibleVersionsPromise) {
|
||||||
|
bibleVersionsPromise = DataCache
|
||||||
|
.fetchWithFallback<any[]>('/data/bible/versions.json')
|
||||||
|
.then((v) => {
|
||||||
|
if (!v || !Array.isArray(v) || v.length === 0) {
|
||||||
|
bibleVersionsPromise = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return bibleVersionsPromise;
|
||||||
|
}
|
||||||
|
|
||||||
function getFallbackCalendarFiles(lang: string): string[] {
|
function getFallbackCalendarFiles(lang: string): string[] {
|
||||||
const files = [
|
const files = [
|
||||||
`/data/${lang}/menaion-bundle.json`,
|
`/data/${lang}/menaion-bundle.json`,
|
||||||
@@ -89,6 +109,7 @@ function getFallbackCalendarFiles(lang: string): string[] {
|
|||||||
|
|
||||||
export class OfflineManager {
|
export class OfflineManager {
|
||||||
private static _progress: PreloadProgress | null = null;
|
private static _progress: PreloadProgress | null = null;
|
||||||
|
private static _running: Promise<PreloadProgress> | null = null;
|
||||||
private static _autoPreloadRequested = false;
|
private static _autoPreloadRequested = false;
|
||||||
|
|
||||||
/** Request an automatic "preload everything" once the Settings view opens. */
|
/** Request an automatic "preload everything" once the Settings view opens. */
|
||||||
@@ -110,28 +131,21 @@ export class OfflineManager {
|
|||||||
|
|
||||||
/** Resolve every Bible translation id from versions.json (single source). */
|
/** Resolve every Bible translation id from versions.json (single source). */
|
||||||
static async getAllBibleTranslationIds(): Promise<string[]> {
|
static async getAllBibleTranslationIds(): Promise<string[]> {
|
||||||
try {
|
const versions = await loadBibleVersions();
|
||||||
const resp = await fetch('/data/bible/versions.json');
|
return versions ? versions.map((v: any) => v.id) : [];
|
||||||
const versions = await resp.json() as any[];
|
|
||||||
return versions.map((v: any) => v.id);
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Discover all book files for a Bible translation from versions.json. */
|
/** Discover all book files for a Bible translation from versions.json. */
|
||||||
static async getBibleFiles(translationPath: string): Promise<string[]> {
|
static async getBibleFiles(translationPath: string): Promise<string[]> {
|
||||||
const files: string[] = [];
|
const files: string[] = [];
|
||||||
try {
|
const versions = await loadBibleVersions();
|
||||||
const resp = await fetch('/data/bible/versions.json');
|
if (!versions) return files;
|
||||||
const versions = await resp.json() as any[];
|
|
||||||
const ver = versions.find((v: any) => v.id === translationPath);
|
const ver = versions.find((v: any) => v.id === translationPath);
|
||||||
if (ver && ver.books) {
|
if (ver && ver.books) {
|
||||||
for (const book of ver.books) {
|
for (const book of ver.books) {
|
||||||
files.push(`/data/${translationPath}/${book.id}.text`);
|
files.push(`/data/${translationPath}/${book.id}.text`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +157,19 @@ export class OfflineManager {
|
|||||||
/**
|
/**
|
||||||
* Preload selected data files for offline use.
|
* Preload selected data files for offline use.
|
||||||
* Returns a PreloadProgress object that can be polled for progress.
|
* Returns a PreloadProgress object that can be polled for progress.
|
||||||
|
* Re-entrant calls join the run already in flight.
|
||||||
*/
|
*/
|
||||||
static async preload(options: PreloadOptions): Promise<PreloadProgress> {
|
static async preload(options: PreloadOptions): Promise<PreloadProgress> {
|
||||||
|
if (OfflineManager._running) return OfflineManager._running;
|
||||||
|
const run = OfflineManager._preloadImpl(options)
|
||||||
|
.finally(() => {
|
||||||
|
if (OfflineManager._running === run) OfflineManager._running = null;
|
||||||
|
});
|
||||||
|
OfflineManager._running = run;
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async _preloadImpl(options: PreloadOptions): Promise<PreloadProgress> {
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
const filesToFetch: string[] = [];
|
const filesToFetch: string[] = [];
|
||||||
const m = await loadManifest();
|
const m = await loadManifest();
|
||||||
@@ -184,6 +209,7 @@ export class OfflineManager {
|
|||||||
done: false,
|
done: false,
|
||||||
failed: 0,
|
failed: 0,
|
||||||
};
|
};
|
||||||
|
if (m === null) progress.manifestMissing = true;
|
||||||
OfflineManager._progress = progress;
|
OfflineManager._progress = progress;
|
||||||
|
|
||||||
// Best-effort: request persistent storage so the OS won't evict data
|
// Best-effort: request persistent storage so the OS won't evict data
|
||||||
@@ -262,15 +288,21 @@ export class OfflineManager {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (notFound) progress.missing = (progress.missing ?? 0) + 1;
|
||||||
// Success or 404 both prove connectivity
|
// Success or 404 both prove connectivity
|
||||||
netFailStreak = 0;
|
netFailStreak = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((progress.missing ?? 0) > 0) {
|
||||||
|
diagLog('preload-missing', { count: progress.missing });
|
||||||
|
}
|
||||||
|
|
||||||
progress.done = true;
|
progress.done = true;
|
||||||
diagLog('preload-done', {
|
diagLog('preload-done', {
|
||||||
total: progress.total,
|
total: progress.total,
|
||||||
failed: progress.failed,
|
failed: progress.failed,
|
||||||
|
missing: progress.missing ?? 0,
|
||||||
ms: Date.now() - startedAt,
|
ms: Date.now() - startedAt,
|
||||||
aborted: progress.aborted === true,
|
aborted: progress.aborted === true,
|
||||||
});
|
});
|
||||||
|
|||||||
+18
-21
@@ -3,11 +3,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getTranslations, type LanguageCode } from '../core/i18n';
|
import { getTranslations, type LanguageCode } from '../core/i18n';
|
||||||
import { OfflineManager } from '../core/offline-manager';
|
import { OfflineManager, type PreloadProgress } from '../core/offline-manager';
|
||||||
import { DataCache } from '../core/data-cache';
|
import { DataCache } from '../core/data-cache';
|
||||||
import { diagTail } from '../core/diag-log';
|
import { diagTail } from '../core/diag-log';
|
||||||
import { isPwaInstalled } from '../core/pwa-install';
|
import { isPwaInstalled } from '../core/pwa-install';
|
||||||
|
|
||||||
|
/** Human message for a finished preload run (shared by manual + auto preload). */
|
||||||
|
function preloadResultText(
|
||||||
|
prog: PreloadProgress,
|
||||||
|
s: { offlineAborted: string; offlineDone: string; offlineFailed: string; offlineDegraded: string; offlineMissing: string },
|
||||||
|
): string {
|
||||||
|
if (prog.aborted) return s.offlineAborted;
|
||||||
|
if (prog.manifestMissing) return s.offlineDegraded;
|
||||||
|
if (prog.failed > 0) {
|
||||||
|
const failList = prog.failedFiles?.length ? `: ${prog.failedFiles.join(', ')}` : '';
|
||||||
|
return `${s.offlineDone} ${prog.failed} ${s.offlineFailed}${failList}`;
|
||||||
|
}
|
||||||
|
if ((prog.missing ?? 0) > 0) return s.offlineMissing.replace('{0}', String(prog.missing));
|
||||||
|
return s.offlineDone;
|
||||||
|
}
|
||||||
|
|
||||||
const LANGUAGES = [
|
const LANGUAGES = [
|
||||||
{ code: 'en', name: 'English', local: 'English' },
|
{ code: 'en', name: 'English', local: 'English' },
|
||||||
{ code: 'ru', name: 'Russian', local: 'Русский' },
|
{ code: 'ru', name: 'Russian', local: 'Русский' },
|
||||||
@@ -563,16 +578,7 @@ export class SettingsView {
|
|||||||
bibleTranslations: bibleTrans,
|
bibleTranslations: bibleTrans,
|
||||||
}).then((prog) => {
|
}).then((prog) => {
|
||||||
if (progressBar) progressBar.style.width = '100%';
|
if (progressBar) progressBar.style.width = '100%';
|
||||||
if (progressText) {
|
if (progressText) progressText.textContent = preloadResultText(prog, t.settings);
|
||||||
if (prog.aborted) {
|
|
||||||
progressText.textContent = t.settings.offlineAborted;
|
|
||||||
} else if (prog.failed > 0) {
|
|
||||||
const failList = prog.failedFiles?.length ? `: ${prog.failedFiles.join(', ')}` : '';
|
|
||||||
progressText.textContent = `${t.settings.offlineDone} ${prog.failed} ${t.settings.offlineFailed}${failList}`;
|
|
||||||
} else {
|
|
||||||
progressText.textContent = t.settings.offlineDone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const diagEl = document.getElementById('diag-log');
|
const diagEl = document.getElementById('diag-log');
|
||||||
if (diagEl) diagEl.textContent = diagTail();
|
if (diagEl) diagEl.textContent = diagTail();
|
||||||
OfflineManager.getStats().then(stats => {
|
OfflineManager.getStats().then(stats => {
|
||||||
@@ -696,16 +702,7 @@ export class SettingsView {
|
|||||||
bibleTranslations: 'all',
|
bibleTranslations: 'all',
|
||||||
}).then((prog) => {
|
}).then((prog) => {
|
||||||
if (progressBar) progressBar.style.width = '100%';
|
if (progressBar) progressBar.style.width = '100%';
|
||||||
if (progressText) {
|
if (progressText) progressText.textContent = preloadResultText(prog, t.settings);
|
||||||
if (prog.aborted) {
|
|
||||||
progressText.textContent = t.settings.offlineAborted;
|
|
||||||
} else if (prog.failed > 0) {
|
|
||||||
const failList = prog.failedFiles?.length ? `: ${prog.failedFiles.join(', ')}` : '';
|
|
||||||
progressText.textContent = `${t.settings.offlineDone} ${prog.failed} ${t.settings.offlineFailed}${failList}`;
|
|
||||||
} else {
|
|
||||||
progressText.textContent = t.settings.offlineDone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const diagEl = document.getElementById('diag-log');
|
const diagEl = document.getElementById('diag-log');
|
||||||
if (diagEl) diagEl.textContent = diagTail();
|
if (diagEl) diagEl.textContent = diagTail();
|
||||||
OfflineManager.getStats().then(stats => {
|
OfflineManager.getStats().then(stats => {
|
||||||
|
|||||||
@@ -26,6 +26,26 @@
|
|||||||
"/data/shared/fasting.json",
|
"/data/shared/fasting.json",
|
||||||
"/data/shared/calendar/triodion.json",
|
"/data/shared/calendar/triodion.json",
|
||||||
"/data/shared/calendar/pentecostarion.json",
|
"/data/shared/calendar/pentecostarion.json",
|
||||||
|
"/data/shared/lives/01.json",
|
||||||
|
"/data/shared/lives/03.json",
|
||||||
|
"/data/shared/lives/05.json",
|
||||||
|
"/data/shared/lives/09.json",
|
||||||
|
"/data/shared/lives/10.json",
|
||||||
|
"/data/shared/lives/11.json",
|
||||||
|
"/data/shared/lives/12.json",
|
||||||
|
"/data/shared/lives/misc/1.json",
|
||||||
|
"/data/shared/lives/misc/2.json",
|
||||||
|
"/data/shared/lives/misc/3.json",
|
||||||
|
"/data/shared/lives/misc/4.json",
|
||||||
|
"/data/shared/lives/misc/5.json",
|
||||||
|
"/data/shared/lives/misc/6.json",
|
||||||
|
"/data/shared/lives/misc/7.json",
|
||||||
|
"/data/shared/lives/misc/8.json",
|
||||||
|
"/data/shared/lives/misc/9.json",
|
||||||
|
"/data/shared/commands/DivineLiturgy.json",
|
||||||
|
"/data/shared/commands/Fasting.json",
|
||||||
|
"/data/shared/commands/ScriptureTransfers.json",
|
||||||
|
"/data/shared/commands/ServiceRules.json",
|
||||||
"/data/en/menaion-bundle.json",
|
"/data/en/menaion-bundle.json",
|
||||||
"/data/shared/services/templates/AllSaints.json",
|
"/data/shared/services/templates/AllSaints.json",
|
||||||
"/data/shared/services/templates/Ambrose.json",
|
"/data/shared/services/templates/Ambrose.json",
|
||||||
@@ -1773,6 +1793,26 @@
|
|||||||
"/data/shared/fasting.json",
|
"/data/shared/fasting.json",
|
||||||
"/data/shared/calendar/triodion.json",
|
"/data/shared/calendar/triodion.json",
|
||||||
"/data/shared/calendar/pentecostarion.json",
|
"/data/shared/calendar/pentecostarion.json",
|
||||||
|
"/data/shared/lives/01.json",
|
||||||
|
"/data/shared/lives/03.json",
|
||||||
|
"/data/shared/lives/05.json",
|
||||||
|
"/data/shared/lives/09.json",
|
||||||
|
"/data/shared/lives/10.json",
|
||||||
|
"/data/shared/lives/11.json",
|
||||||
|
"/data/shared/lives/12.json",
|
||||||
|
"/data/shared/lives/misc/1.json",
|
||||||
|
"/data/shared/lives/misc/2.json",
|
||||||
|
"/data/shared/lives/misc/3.json",
|
||||||
|
"/data/shared/lives/misc/4.json",
|
||||||
|
"/data/shared/lives/misc/5.json",
|
||||||
|
"/data/shared/lives/misc/6.json",
|
||||||
|
"/data/shared/lives/misc/7.json",
|
||||||
|
"/data/shared/lives/misc/8.json",
|
||||||
|
"/data/shared/lives/misc/9.json",
|
||||||
|
"/data/shared/commands/DivineLiturgy.json",
|
||||||
|
"/data/shared/commands/Fasting.json",
|
||||||
|
"/data/shared/commands/ScriptureTransfers.json",
|
||||||
|
"/data/shared/commands/ServiceRules.json",
|
||||||
"/data/shared/services/templates/AllSaints.json",
|
"/data/shared/services/templates/AllSaints.json",
|
||||||
"/data/shared/services/templates/Ambrose.json",
|
"/data/shared/services/templates/Ambrose.json",
|
||||||
"/data/shared/services/templates/Annunciation.json",
|
"/data/shared/services/templates/Annunciation.json",
|
||||||
@@ -3113,6 +3153,26 @@
|
|||||||
"/data/shared/fasting.json",
|
"/data/shared/fasting.json",
|
||||||
"/data/shared/calendar/triodion.json",
|
"/data/shared/calendar/triodion.json",
|
||||||
"/data/shared/calendar/pentecostarion.json",
|
"/data/shared/calendar/pentecostarion.json",
|
||||||
|
"/data/shared/lives/01.json",
|
||||||
|
"/data/shared/lives/03.json",
|
||||||
|
"/data/shared/lives/05.json",
|
||||||
|
"/data/shared/lives/09.json",
|
||||||
|
"/data/shared/lives/10.json",
|
||||||
|
"/data/shared/lives/11.json",
|
||||||
|
"/data/shared/lives/12.json",
|
||||||
|
"/data/shared/lives/misc/1.json",
|
||||||
|
"/data/shared/lives/misc/2.json",
|
||||||
|
"/data/shared/lives/misc/3.json",
|
||||||
|
"/data/shared/lives/misc/4.json",
|
||||||
|
"/data/shared/lives/misc/5.json",
|
||||||
|
"/data/shared/lives/misc/6.json",
|
||||||
|
"/data/shared/lives/misc/7.json",
|
||||||
|
"/data/shared/lives/misc/8.json",
|
||||||
|
"/data/shared/lives/misc/9.json",
|
||||||
|
"/data/shared/commands/DivineLiturgy.json",
|
||||||
|
"/data/shared/commands/Fasting.json",
|
||||||
|
"/data/shared/commands/ScriptureTransfers.json",
|
||||||
|
"/data/shared/commands/ServiceRules.json",
|
||||||
"/data/cu/menaion-bundle.json",
|
"/data/cu/menaion-bundle.json",
|
||||||
"/data/shared/services/templates/AllSaints.json",
|
"/data/shared/services/templates/AllSaints.json",
|
||||||
"/data/shared/services/templates/Ambrose.json",
|
"/data/shared/services/templates/Ambrose.json",
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ export default defineConfig({
|
|||||||
globIgnores: ['**/data/**'],
|
globIgnores: ['**/data/**'],
|
||||||
maximumFileSizeToCacheInBytes: 20 * 1024 * 1024,
|
maximumFileSizeToCacheInBytes: 20 * 1024 * 1024,
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
|
{
|
||||||
|
// The preload file list must be fresh when online; a stale copy
|
||||||
|
// silently loses coverage. Offline falls back to the SW copy.
|
||||||
|
urlPattern: /\/data\/shared\/preload-manifest\.json$/,
|
||||||
|
handler: 'NetworkFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'ponomar-manifest',
|
||||||
|
networkTimeoutSeconds: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
urlPattern: /\/data\/.*\.json$/,
|
urlPattern: /\/data\/.*\.json$/,
|
||||||
handler: 'StaleWhileRevalidate',
|
handler: 'StaleWhileRevalidate',
|
||||||
|
|||||||
Reference in new issue
Block a user