惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
Stack Overflow Blog
Stack Overflow Blog
量子位
腾讯CDC
N
Netflix TechBlog - Medium
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
S
SegmentFault 最新的问题
A
About on SuperTechFans
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tailwind CSS Blog
G
Google Developers Blog
U
Unit 42
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
雷峰网
雷峰网
罗磊的独立博客
Vercel News
Vercel News
L
LangChain Blog
V
V2EX
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
博客园 - Franky
V
Visual Studio Blog
J
Java Code Geeks

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I built Google Drive sync without a backend (and the ...
Dean · 2026-06-16 · via DEV Community

Dean

When I started building PenPage — a privacy-first note app that
stores everything in IndexedDB — I made one assumption that cost me
three weeks of debugging:

"Google Drive sync will be the easy part."

It wasn't.

Here's what I learned building a sync engine entirely in the
browser, with no backend server.


## The core idea: one file to rule them all

Instead of syncing every note file individually, I built around
a single sync.json that stores all metadata:

  ppage-app/
  ├── sync.json        ← the source of truth
  ├── pages/
  │   └── page-*.md   ← actual note content
  └── images/

sync.json holds folder structure, page metadata, image metadata,
and device info — but NOT page content. On every sync:

  1. Download sync.json (or skip if modifiedTime hasn't changed)
  2. Compare local IndexedDB state vs Drive state
  3. Upload/download only what changed
  4. Upload the new sync.json

This keeps API calls to 2-3 per sync cycle instead of N×2 per file.


## Bug #1: The 404 that wasn't really a 404

Google Drive returns 404 when you try to access a folder that's
been deleted and recreated — even if a folder with the same name
exists now.

This hit me when implementing "Force Upload" (which recreates the
app folder from scratch). Device A would force upload, delete and
recreate the folder. Device B still had the old folder ID cached
— and every API call returned 404.

The fix: wrap every Drive operation in a recovery handler:

  private async withFolderRecovery<T>(
    operation: () => Promise<T>
  ): Promise<T> {
    try {
      return await operation()
    } catch (error) {
      if (error.message.includes('404')) {
        await this.reinitialize() // re-fetch all folder IDs
        return await operation()  // retry once
      }
      throw error
    }
  }

Any 404 triggers a full folder ID refresh, then retries. Simple,
but it took me a while to realize the root cause.


## Bug #2: The silent data corruption hiding in a sentinel value

Every sync, I run a cleanup step that repairs folder parentId
values. The check looked like this:

  // Intended: fix folders with wrong parentId
  if (isRootParentId(folder.parentId)) {
    repairFolder(folder)
  }

isRootParentId() returned true for both 'workspace' (the
actual sentinel for "orphaned folder") AND 'root' (the correct
value for top-level user folders).

Result: every sync, ALL top-level folders got their updatedAt
timestamp refreshed to Date.now(). The comparison logic saw
local as newer than Drive → uploaded everything → silently
overwrote changes from other devices.

The fix:

  // Only match the actual bad sentinel value
  if (folder.parentId === 'workspace') {
    repairFolder(folder)
  }

One character difference. Weeks of mysterious "my changes
disappeared" reports.


## Bug #3: IndexedDB index queries don't match undefined

Force Download is supposed to: clear local data → import from Drive.

But after Force Download, pages appeared blank. The root cause was
a chain of four silent failures:

  1. clearAllData() queries IndexedDB by workspaceId: 'global'
  2. Old records had workspaceId: undefined (pre-migration data)
  3. IndexedDB index queries are exact matchundefined'global'
  4. Old records survived the clear
  5. importAll() tried to create records with same IDs → store.add() silently fails on duplicate keys
  6. New records never written → UI shows nothing
  // ❌ Misses orphan records where workspaceId is undefined
  const pages = await db.getAllPages({ workspaceId: 'global' })

  // ✅ Explicit orphan cleanup
  const allPages = await db.getAllPages()
  const orphans = allPages.filter(p => !p.workspaceId)
  await deleteAll(orphans)

Lesson: store.add() failure on duplicate keys is silent.
store.put() overwrites. Know which one you're using.


## What actually works well

Despite the bugs, the architecture held up:

  • modifiedTime as a proxy for "changed" — no polling, no webhooks, no server
  • Parallel uploads (5 concurrent) reduced sync time 80% for large note sets
  • Tombstones in sync.json for deleted pages — other devices learn about deletions without needing the deleted file

## Would I do it again?

Yes, but I'd budget 3× more time for edge cases. The Google Drive
API docs describe the happy path. The bugs live in:

  • Stale cached folder IDs
  • Silent IndexedDB failures
  • Sentinel value collisions in your own data model

If you're building anything with Google Drive API or browser-side
sync, happy to answer questions in the comments.

PenPage: https://penpage.com