Dropbox Synchronization For PGN Files

Learning Lab
My Journey Through Books, Discoveries, and Ideas

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 Dropbox API 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 the server_modified timestamp 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 in localStorage. When the connection is restored (online event), the application checks this flag and triggers coordinateSync to 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.