WS-2088: SPIKE: PWA offline article: implement SW caching mechanism#13755
Draft
WS-2088: SPIKE: PWA offline article: implement SW caching mechanism#13755
Conversation
…t-sw-caching-mechanism
…a-offline-article-implement-sw-caching-mechanism
andrewscfc
reviewed
Mar 4, 2026
Comment on lines
+67
to
+74
| const db = await openDB(); | ||
| return new Promise((resolve, reject) => { | ||
| const tx = db.transaction(store, 'readwrite'); | ||
| const req = tx.objectStore(store).delete(key); | ||
| req.onsuccess = () => resolve(); | ||
| req.onerror = e => reject(e.target.error); | ||
| }); | ||
| }; |
Contributor
There was a problem hiding this comment.
Suggested change
| const db = await openDB(); | |
| return new Promise((resolve, reject) => { | |
| const tx = db.transaction(store, 'readwrite'); | |
| const req = tx.objectStore(store).delete(key); | |
| req.onsuccess = () => resolve(); | |
| req.onerror = e => reject(e.target.error); | |
| }); | |
| }; | |
| const db = await openDB(); | |
| const tx = db.transaction(store, 'readwrite'); | |
| await tx.objectStore(store).delete(key); | |
| }; |
Is there a reason why need all these promise wrappers around these db operations?
Comment on lines
+196
to
+202
| const isMissing = !existing; | ||
| const isOutdated = | ||
| !!existing && | ||
| article.timestamp && | ||
| existing.timestamp !== article.timestamp; | ||
|
|
||
| if (!isMissing && !isOutdated) return; |
Contributor
There was a problem hiding this comment.
Suggested change
| const isMissing = !existing; | |
| const isOutdated = | |
| !!existing && | |
| article.timestamp && | |
| existing.timestamp !== article.timestamp; | |
| if (!isMissing && !isOutdated) return; | |
| const isCached = existing; | |
| const isOutdated = | |
| !isCached && | |
| article.timestamp && | |
| existing.timestamp !== article.timestamp; | |
| if (isCached && !isOutdated) return; |
I found this very difficult to read so just suggesting a change to get away from the negative conditons
andrewscfc
reviewed
Mar 5, 2026
elvinasv
commented
Mar 5, 2026
| }; | ||
|
|
||
| const cacheArticles = async service => { | ||
| const lastSync = await dbGet('meta', 'lastArticleSync'); |
Member
Author
There was a problem hiding this comment.
TODO:
potentially we should save this per service, i.e. lastArticleSync_${service}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves JIRA: https://bbc.atlassian.net/browse/WS-2088

How PWA offline article caching works
Overview
When a user installs the PWA, the service worker proactively caches a set of most-read articles per service, enabling offline reading. The feature is controlled by a per-service feature toggle (
offlineArticle).Caching flow
1. Offline page as data source
The offline page (
/{service}/offline) is enriched server-side with amost-read-datascript tag containing article metadata (URLs, timestamps). This makes the offline page the single source of truth for which articles to cache.2. Periodic offline page refresh
On each PWA page load, the SW checks whether a refresh is due (default interval: 24h, controlled by
REFRESH_INTERVAL_MS). If due, the offline page and its associated resources (scripts, stylesheets) are force-fetched and stored in the Cache API, replacing any stale version.3. Article caching
After the offline page is refreshed, the SW parses the
most-read-datafrom the cached HTML and for each article:timestampagainst what is stored in IndexedDBurl,timestamp,cachedAt) in IndexedDB (cachedArticlesstore)MAX_ARTICLE_AGE_MS)4. Feature toggle gate
The
offlineArticletoggle is evaluated client-side inuseSendPWAStatus(reading fromToggleContext+ServiceContextfor the per-service check). It is passed to the SW via thePWA_STATUSmessage asofflineArticle: { isEnabled, service }and persisted to IndexedDB (offlineArticleEnabled_{service}). Article caching is skipped entirely if the toggle is disabled. The offline page itself is always cached for PWA users regardless of the toggle.5. Serving cached content during navigation
On a navigation fetch event, if the network request fails or returns 5xx:
offlineArticleEnabled_{service}from IndexedDB (lazy — only on failure)MAX_ARTICLE_AGE_MS6. Sync triggered on page load
The caching cycle is initiated on every PWA page load via
useSendPWAStatus→PWA_STATUSmessage → SW message handler. TheREFRESH_INTERVAL_MSguard prevents unnecessary work on frequent visits.Things to consider for follow-up
Page type filtering
Not all page types are suitable for offline caching. A predefined list of cacheable page types should be established, excluding:
This could be enforced either at the point of building the most-read list (server-side, filtering the data injected into the offline page) or in the SW before caching each article URL.
Periodic Background Sync
Currently the sync is tied to page load — if the user does not open the PWA for 24h+ the cached articles will be stale until the next visit. The Periodic Background Sync API would allow the SW to refresh articles in the background on a schedule, independent of page loads. This requires user permission and is currently only supported in Chromium-based browsers, so it should be treated as a progressive enhancement alongside the existing page-load-triggered sync.