Building a PWA Template: Dropbox sync, i18n and offline
This is the second part of the series detailing the creation of a generic Progressive Web Application (PWA) template. In the previous post here, we covered the basic structure, styling, core logic, storage, and UI components. Now, we’ll explore the more advanced features: Dropbox synchronization, internationalization (i18n), offline capabilities, and logging.
JavaScript implementation: Dropbox integration
A core feature is the synchronization of files stored in the app’s dedicated Dropbox folder using the official Dropbox JavaScript SDK.
dropbox-sync.js: Orchestrates the initialization of authentication and offline handling.dropbox/config.js: Stores the Dropbox App Key (CLIENT_ID- needs replacement) and calculates theREDIRECT_URI.dropbox/auth.js: Manages the OAuth 2.0 flow, storing the access token inlocalStorage. It also discovers files in the Dropbox app folder upon login.dropbox/api.js: Wraps Dropbox SDK functions (filesGetMetadata,filesDownload,filesUpload,filesMoveV2,filesDeleteV2) for interacting with the API, including error handling for invalid tokens.dropbox/ui.js: Updates the sync status indicator (#syncStatusIndicator) and handles the conflict resolution modal (#conflictModal).dropbox/offline.js: Detects network status changes (online/offline) and triggers pending uploads when connectivity is restored.-
sync-coordinator.js: Contains the core synchronization logic.- Listens for
localDataChangedevents from the storage module. - Debounces sync checks to avoid excessive API calls.
- Compares local and Dropbox file timestamps (
coordinateSyncfunction). - Handles conflicts by prompting the user via the conflict modal if both local and remote versions have changed.
- Automatically uploads or downloads based on timestamp comparison when there’s no conflict.
- Listens for
// Simplified Sync Coordination Logic in sync-coordinator.js
async function coordinateSync(filePath) {
// 1. Get local metadata (timestamp) from localStorage
// 2. Get Dropbox metadata (timestamp) via dropbox/api.js
// 3. Compare timestamps:
if (dropboxNewer && localHasPendingChanges) {
// Show conflict modal using dropbox/ui.js
const resolution = await showConflictModal();
if (resolution === 'dropbox') await downloadFile(filePath);
else await uploadFile(filePath); // Overwrite Dropbox with local
} else if (dropboxNewer) {
await downloadFile(filePath);
} else if (localNewer) {
await uploadFile(filePath);
} else {
// Files are in sync or one doesn't exist (handle initial up/download)
}
// 4. Update sync status via dropbox/ui.js
}
// Listen for local changes and trigger debounced sync
window.addEventListener('localDataChanged', event => {
if (event.detail.filePath === getActiveFilePath()) {
debouncedSync(event.detail.filePath);
}
});
This robust system ensures data consistency across devices while handling potential conflicts and offline scenarios.
JavaScript implementation: internationalization
Multi-language support is implemented using i18next and its plugins.
- Initialization: Configures
i18nextwith the browser language detector and HTTP backend to load translation JSON files (assets/locales/{lang}/translation.json). - Content Update: Selects elements with
data-i18nattributes and updates their content based on the loaded translations. - Language Switching: Handles clicks on language selection links, changes the
i18nextlanguage, updates the UI, and manages the current language display in the header.
// i18next Initialization in locales.js
i18next
.use(i18nextBrowserLanguageDetector)
.use(i18nextHttpBackend)
.init({
fallbackLng: 'en',
debug: true, // Set to false in production
backend: {
loadPath: '/assets/locales/{{lng}}/{{ns}}.json',
},
detection: {
order: ['querystring', 'localStorage', 'navigator'],
caches: ['localStorage'],
},
}, (err, t) => {
if (err) return console.error('i18next initialization failed', err);
updateContent(); // Apply translations
document.body.classList.add('localized'); // Show body (FOUC prevention)
});
// Function to apply translations
function updateContent() {
document.querySelectorAll('[data-i18n]').forEach(element => {
const key = element.getAttribute('data-i18n');
// Handle text content and attributes like 'title'
// ... logic to apply i18next.t(key) ...
});
}
JavaScript implementation: caching and PWA
Offline functionality is provided by a Service Worker and a cache management script.
-
service-worker.js: (Standard implementation assumed)- Installation: Caches essential app assets (HTML, CSS, JS, images).
- Activation: Cleans up old caches.
- Fetch: Implements a cache-first strategy, serving assets from the cache when available and falling back to the network. Handles navigation requests appropriately.
- Message Handling: Listens for messages like
refresh_cacheto update cached assets.
-
cache.js:- Fetches a version file (
/data/json/version.json) from the server. - Compares the fetched version with the version stored in
localStorage. - If the versions differ, it sends a
refresh_cachemessage to the active service worker to trigger an update of the cached assets and updates the stored version.
- Fetches a version file (
// Simplified Cache Update Logic in cache.js
async function checkCacheVersion() {
try {
const response = await fetch('/data/json/version.json?_=' + Date.now()); // Prevent browser caching
const serverVersionData = await response.json();
const serverVersion = serverVersionData.version;
const localVersion = localStorage.getItem('appVersion');
if (serverVersion && serverVersion !== localVersion) {
console.log(`New version found: {serverVersion}. Local: {localVersion}. Refreshing cache.`);
localStorage.setItem('appVersion', serverVersion);
// Send message to service worker
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({ action: 'refresh_cache' });
}
}
} catch (error) {
console.error('Error checking cache version:', error);
}
}
JavaScript implementation: logging
A simple utility for conditional console logging during development.
- Provides
logVerboseandwarnVerbosefunctions. - Output is only generated if a global
VERBOSE_LOGGING_ENABLEDflag is set totrue.
// logging.js
const VERBOSE_LOGGING_ENABLED = true; // Set to false for production
function logVerbose(...args) {
if (VERBOSE_LOGGING_ENABLED) {
console.log(...args);
}
}
function warnVerbose(...args) {
if (VERBOSE_LOGGING_ENABLED) {
console.warn(...args);
}
}
Conclusion
This PWA template provides a solid starting point for web applications requiring offline support, file management, and cloud synchronization via Dropbox. By modularizing the JavaScript and leveraging libraries like Bootstrap and i18next, it offers a flexible and feature-rich foundation. The Dropbox integration, with its attention to conflict resolution and offline handling, adds significant value for applications needing data persistence across devices. The combination of Service Workers for offline access and i18next for internationalization makes it adaptable for a global user base.
You can access the full source code, at the GitHub repository here.
For more insights into this topic, you can find the details here.