Read Frog Google Drive Backup Implementation: Sync Mechanism and Conflict Resolution

Read Frog synchronizes user configurations with Google Drive by storing a single JSON file in the app-data folder, comparing timestamps between local and remote versions, and resolving conflicts through deep equality checks or manual user intervention when divergent changes are detected.

Read Frog, an open-source browser extension available at mengxi-ream/read-frog, implements a robust Google Drive backup system that ensures user configurations remain synchronized across devices. The system handles OAuth authentication with automatic token refresh, maintains a single source of truth in the user's Drive app-data folder, and employs a timestamp-based sync algorithm to detect and resolve conflicts. This article examines the complete implementation, from low-level API interactions to the conflict resolution logic that maintains data consistency.

OAuth Authentication and Token Management

The authentication layer in src/utils/google-drive/auth.ts manages the complete OAuth lifecycle. The authenticateGoogleDriveAndSaveTokenToStorage function (lines 57-96) builds the Google auth URL, launches the interactive flow, and persists the resulting credentials. The access token, expiry timestamp, and token type are stored under the key __googleDriveToken (defined as the constant GOOGLE_DRIVE_TOKEN_STORAGE_KEY) using storage.setItem (lines 92-95).

Token validation occurs in getValidAccessToken (lines 107-118), which retrieves the stored token, validates its shape with Zod, and checks against a 1-minute expiry buffer. If the token is missing or about to expire, the system automatically re-runs the OAuth flow. After successful authentication, getGoogleUserInfo (lines 155-175) fetches the user's email address to include in sync metadata.

The React hook useGoogleDriveAuth in src/hooks/use-google-drive-auth.ts wraps this logic, exposing a react-query query that automatically invalidates when the token changes in storage, ensuring the UI always reflects the current authentication state.

Remote Configuration File Handling

All remote operations target a single file named read-frog-config.json inside the Google Drive app-data folder. The low-level REST API functions reside in src/utils/google-drive/api.ts:

  • findFileInAppData (lines 27-45) locates the configuration file and returns its Drive ID
  • downloadFile (lines 64-72) fetches file content given a valid ID
  • uploadFile (lines 94-115) creates new files or overwrites existing ones when an ID is supplied

High-level abstractions in src/utils/google-drive/storage.ts handle schema migration and metadata extraction. The getRemoteConfigAndMetaWithUserEmail function (lines 11-52) obtains the authenticated user's email, downloads the JSON, migrates it to the current schema, and returns both the configuration and its metadata. Conversely, setRemoteConfigAndMeta (lines 59-66) uploads the JSON, creating the file in the app-data folder if it does not exist.

Synchronization Algorithm and Conflict Resolution

The core sync routine in src/utils/google-drive/sync.ts implements a three-way merge strategy comparing local state, remote state, and the last-known synchronized snapshot. It returns a SyncResult indicating success, conflict, or specific error conditions.

Data Retrieval Phase

The algorithm loads three data sources simultaneously (lines 66-69):

  1. Local configuration via getLocalConfigAndMeta
  2. Last-synced snapshot via getLastSyncedConfigAndMeta
  3. Remote configuration via getRemoteConfigAndMetaWithUserEmail

First-Time Pairing and Email Validation

If the remote user's email differs from the last-synced metadata (or no snapshot exists), the system handles initial setup (lines 73-90):

  • Remote exists: Download the remote configuration, replace the local copy, and record the new email in lastSyncedConfig
  • Remote missing: Upload the local configuration to Drive and establish the initial sync record

Change Detection Logic

The system compares lastModifiedAt timestamps to determine the sync direction (lines 94-96):

const localChangedSinceSync  = localConfig.meta.lastModifiedAt  > lastSynced.meta.lastModifiedAt;
const remoteChangedSinceSync = remoteConfig && remoteConfig.meta.lastModifiedAt > lastSynced.meta.lastModifiedAt;
  • Only local changed → Upload to Drive
  • Only remote changed → Download to local storage
  • Neither changed → No action required
  • Both changed → Trigger conflict resolution

Conflict Resolution Strategy

When both sides have changed, the algorithm uses dequal to check deep equality (lines 100-129):

  • Identical content: Treat as "same-changes" and re-upload to normalize timestamps across devices
  • Different content: Return {status: "unresolved", data: {base, local, remote}} to trigger the UI conflict resolver, allowing the user to choose which version to keep

After any successful upload or download, setLastSyncConfigAndMeta updates the snapshot with the current timestamp and user email, establishing the new baseline for future comparisons.

UI Integration and Manual Sync Triggers

Components integrate the backup system through the useGoogleDriveAuth hook and the syncConfig function. When authQuery.data?.isAuthenticated is true, calling await syncConfig() executes the full synchronization workflow and returns a typed result indicating the action taken: "uploaded", "downloaded", "same-changes", "no-change", or "unresolved".

import { syncConfig } from "@/utils/google-drive/sync";
import { toast } from "sonner";

export function SyncButton() {
  const handleClick = async () => {
    const result = await syncConfig();
    if (result.status === "success") {
      toast.success(`Config ${result.action}`);
    } else if (result.status === "unresolved") {
      toast.warning("Sync conflict – open the conflict resolver.");
    } else {
      toast.error(`Sync failed: ${result.error.message}`);
    }
  };

  return <button onClick={handleClick}>Sync with Google Drive</button>;
}

Summary

  • Authentication Layer: src/utils/google-drive/auth.ts manages OAuth flows, token storage under __googleDriveToken, and automatic refresh with a 1-minute expiry buffer.
  • Remote Storage: src/utils/google-drive/storage.ts abstracts the read-frog-config.json file in Drive's app-data folder, handling schema migration and email metadata extraction.
  • Sync Engine: src/utils/google-drive/sync.ts implements timestamp-based change detection and three-way conflict resolution, delegating to the UI when manual intervention is required.
  • React Integration: useGoogleDriveAuth and syncConfig provide declarative hooks for UI components to initiate backup operations and respond to authentication state changes.

Frequently Asked Questions

How does Read Frog handle expired OAuth tokens?

The getValidAccessToken function in src/utils/google-drive/auth.ts validates the stored token against a 1-minute expiry buffer. If the token is missing or about to expire, the system automatically re-initiates the OAuth flow through authenticateGoogleDriveAndSaveTokenToStorage, ensuring uninterrupted backup operations without manual user intervention.

What happens when the same configuration is modified on two devices simultaneously?

When both local and remote configurations have changed since the last sync (detected via lastModifiedAt timestamps), the algorithm in src/utils/google-drive/sync.ts first checks deep equality using dequal. If the content is identical, it treats the changes as equivalent and re-uploads to normalize timestamps. If the content differs, it returns an "unresolved" status, prompting the UI to display a conflict resolution dialog for manual user selection.

Where exactly is the backup file stored in Google Drive?

According to the implementation in src/utils/google-drive/api.ts, the backup file named read-frog-config.json is stored in the Google Drive app-data folder, which is a hidden directory accessible only to the Read Frog application. The findFileInAppData function (lines 27-45) queries this specific location using the Drive API's spaces parameter set to "appDataFolder".

How does the sync system detect which version is newer?

The system compares lastModifiedAt timestamps between three states: the current local configuration, the remote Drive file, and the last-synced snapshot stored locally. In src/utils/google-drive/sync.ts (lines 94-96), boolean flags localChangedSinceSync and remoteChangedSinceSync determine whether to upload, download, or flag a conflict based on whether each side's timestamp exceeds the baseline snapshot timestamp.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →