Dropbox synchronization for PGN files
Continuing from the previous post here, a core feature adapted from the underlying template is the optional synchronization with Dropbox. This allows users to manage their PGN files in a dedicated folder within their Dropbox account and keep them consistent across devices.
- Authentication: The app uses the Dropbox JavaScript SDK to handle the OAuth 2.0 flow (specifically, the secure PKCE flow). Users grant the app permission to access its own dedicated folder within their Dropbox. Access tokens (and refresh tokens for longer sessions) are securely stored in
localStorage.
// Simplified PKCE Authentication Initiation (from dropbox/auth.js)
import { CLIENT_ID, REDIRECT_URI } from './config.js';
// ... PKCE helper functions (generateRandomString, sha256, etc.) ...
export async function authenticateWithDropbox() {
const dbxAuth = new Dropbox.DropboxAuth({ clientId: CLIENT_ID });
const codeVerifier = generateRandomString(128);
try {
const codeChallenge = await generateCodeChallenge(verifier);
sessionStorage.setItem('dropboxCodeVerifier', codeVerifier); // Store verifier
// Construct auth URL with PKCE parameters
const params = new URLSearchParams({ /* ... client_id, redirect_uri, code_challenge ... */ });
const authUrl = `https://www.dropbox.com/oauth2/authorize?${params.toString()}`;
window.location.href = authUrl; // Redirect user
} catch (error) { /* Handle error */ }
}
- File Operations: Once connected, the application uses the
DropboxAPI client (dbx) to interact with files. A wrapper function (callApiWithRefresh) handles potential token expiry by attempting a refresh using the stored refresh token before retrying the API call.
// Example: Uploading a file (from dropbox/api.js)
export async function uploadFileToDropbox(filePath, fileContent) {
// ... checks ...
try {
const response = await callApiWithRefresh( // Use the wrapper
() => dbx.filesUpload({
path: filePath,
contents: fileContent,
mode: 'overwrite',
autorename: false,
mute: true
}),
`filesUpload(${filePath})` // Operation name for logging
);
return true; // Success
} catch (error) {
console.error(`Final error uploading ${filePath}:`, error);
return false; // Failure
}
}
Other operations like filesDownload, filesGetMetadata, filesMoveV2, filesDeleteV2 are implemented similarly using the callApiWithRefresh wrapper.
- Conflict Resolution: Synchronization compares the last modified timestamp of the local file version (tracked in
localStorage) with theserver_modifiedtimestamp from Dropbox metadata.
// Simplified Sync Logic (from sync-coordinator.js)
async function coordinateSync() {
// ... get activeFilePath, dbx instance ...
const localTimestampStr = getLocalLastModified(); // Get from localStorage
const dropboxMeta = await getDropboxFileMetadata(activeFilePath); // API call
const localDate = localTimestampStr ? new Date(localTimestampStr) : null;
const dropboxDate = dropboxMeta?.server_modified ? new Date(dropboxMeta.server_modified) : null;
if (/* Dropbox is newer and local changes pending */) {
logVerbose(`Conflict detected for ${activeFilePath}`);
const userChoice = await showConflictModal(localDate, dropboxDate, activeFilePath); // Show UI modal
if (userChoice === 'local') { /* Upload local */ }
else if (userChoice === 'dropbox') { /* Download Dropbox */ }
} else if (/* Local is newer */) {
/* Upload local */
} else if (/* Dropbox is newer, no pending local */) {
/* Download Dropbox */
} else {
/* Assume synced or handle initial cases */
}
// ... update sync status indicator ...
}
- Offline Handling: If the user edits a PGN file while offline, the change is saved locally. A flag (
pending_upload/...) is set inlocalStorage. When the connection is restored (onlineevent), the application checks this flag and triggerscoordinateSyncto upload the pending changes.
// Simplified Online Event Handler (from dropbox/offline.js)
async function handleOnlineStatus() {
logVerbose('Application came online.');
const activeFilePath = getActiveFile();
if (isUploadPending(activeFilePath)) { // Check localStorage flag
logVerbose(`Pending upload detected for ${activeFilePath}. Triggering sync...`);
await coordinateSync(); // Attempt sync
} else {
updateSyncIndicator(SyncStatus.IDLE, '', activeFilePath);
}
}
window.addEventListener('online', handleOnlineStatus);
This combination of PWA features and robust Dropbox sync provides a flexible and reliable platform for managing and viewing PGN files, whether you’re online or offline.
You can access the full source code, at the GitHub repository here.
For more insights into this topic, you can find the details here.