Skip to content

Conversation

@bstrackany
Copy link
Contributor

@bstrackany bstrackany commented Dec 16, 2025

Add Google Drive Sync Feature

Overview

resolves #200

Adds client-side Google Drive sync as an alternative to Warehouse for remote data storage. Users can now sync their Forge Steel data (heroes, homebrew.) to their personal Google Drive using the appDataFolder scope - no backend required, and no access required to a user's general Google Drive.

Changes

New Files

  • src/utils/google-drive-client.ts - Lightweight Drive v3 REST client using GIS
  • src/utils/remote-google-drive-data-service.ts - DataService implementation for Drive storage
  • src/components/panels/connection-settings/google-drive-settings-panel.tsx - Panel for managing google drive
  • .env.example - Template for environment variables

Modified Files

  • Most changes were made to the connection settings panel and the transfer page.
  • src/models/connection-settings.ts - Added useGoogleDrive and googleClientId fields
  • src/logic/update/connection-settings-update-logic.ts - Added defaults for new fields
  • src/utils/feature-flags.ts - Added remoteGoogleDrive feature flag
  • src/components/pages/transfer/transfer-page.tsx - Added gdrive service into transfer logic, some UX updates
  • src/components/modals/settings/settings-modal.tsx - Added section for Google Drive
  • src/utils/feature-flags.ts - Added google drive feature flag
  • .gitignore - Added .env and .env.local to ignore list

How It Works

  1. Authentication: Uses Google Identity Services token client for browser-based OAuth
  2. Storage: Creates a single JSON file (forgesteel-data.json) in Drive's appDataFolder
  3. Scope: Only requests https://www.googleapis.com/auth/drive.appdata (sensitive but not restricted)
  4. Data structure: Matches Warehouse format - single blob with document IDs as keys

IMPORTANT! Required setup for non-local deployment

  1. Create OAuth 2.0 Client ID in Google Cloud Console:

  2. Set environment variable:

    • Create the .env file (copy .env.example) or some other way of setting ENV variables
    • Update the file
    VITE_GDRIVE_CLIENT_ID=your-client-id-here.apps.googleusercontent.com
    

User Experience

  1. Open Settings → Admin
  2. Add google-drive feature flag
  3. Go to Google Drive Settings panel
  4. Toggle "Connect with Google Drive" ON
  5. Click "Connect Google Drive" → Approve consent popup, including being logged in w a Google account
  6. Connection status updates to green "Connected" tag
  7. Save settings and reload app
  8. Open Settings → Admin
  9. Go to Google Drive Settings panel, click Transfer Data button
  10. Use buttons to transfer data between local and cloud

Testing Checklist

  • Connect Google Drive with valid OAuth client
  • Upload data from local to Drive
  • Download data from Drive to local
  • Reconnect after token expiry
  • Disconnect and revoke access
  • Handle missing/corrupted Drive file gracefully
  • Multiple device sync scenario

Breaking Changes

Hopefully none - this is a new optional feature behind a feature flag.

Known Issues

  • The app will refresh the Google token if it expires (they expire after 1 hour). Unfortunately, Google does that via popups. So for now, the user should allow popups in order for the Google connection to stay seamlessly connected. The app does try to refresh the token when the user clicks to go to the transfer page, so if they have popups blocked it's not the end of the world. The proper fix to avoid any popups and get lasting tokens is to implement refresh tokens which unfortunately requires a backend.
  • I realized the transfer page does a merge when pushing to the data storage, which I think means you're unable to overwrite the cloud if you've deleted a hero locally. Probably need an "overwrite" option in transfer page in the future, maybe with some UX updates around merging vs just straight copy/overwrite.

NOTES

  • For local testing only (http://localhost:5173/forgesteel/) if you want you can use my Client ID (it only works on localhost) and put it into your ENV file. HMU if you want that client ID, basically make an env file, put the client ID in, and fire up the app locally and you'll be able to see the google drive stuff in action.
  • For production, someone will need to create a production Google Client ID with all the right permissions and pointed at the various prod URLs (and staging/dev if there are some).
  • IMO this is a beta feature and should stay behind a feature flag for now. Down the road after more testing maybe it can be shared publicly, or tied into UX like auto-sync etc.
  • Big thanks for Viritas1000 for creating the transfer system I was able to plug into.

Screenshots

image image

@bstrackany bstrackany marked this pull request as draft December 16, 2025 04:40
@bstrackany bstrackany marked this pull request as ready for review December 16, 2025 05:14
@Veritas1000
Copy link
Contributor

Just for my clarification, is the intention for this that Andy would create the OAuth client, not individual users?

@bstrackany
Copy link
Contributor Author

Just for my clarification, is the intention for this that Andy would create the OAuth client, not individual users?

Yes. The ForgeSteel OAuth client would be created once by Andy or someone official, and would be the thing that users are granting permission to access their Google Drive's appData folder. I went with appData so that users wouldn't have to grant permissions to their actual drive files, for security/privacy concerns.

@bstrackany
Copy link
Contributor Author

Also I'd suggest a squash if you guys accept this PR.

@Veritas1000
Copy link
Contributor

Just a heads up there are a ton of linting errors when I run npm run lint on this branch. I recommend resolving those as well.

@Veritas1000
Copy link
Contributor

What is the expected error flow for a user? I'm in the process of trying this out, and once I connected via OAuth, nothing appears to have changed - the data still seems to be coming from the local storage. Then when I check the console, I see lots of 403 errors from all of the google api calls (I think that is just the client configuration taking time propagating through the google services, but maybe I missed something there). However, if I wasn't a developer, I'd have no way of knowing that something was going wrong.

@Veritas1000
Copy link
Contributor

A couple things I've found in testing:

There might be something missing in the setup instructions for the OAuth client? I am pretty sure I followed all of the instructions here, but I keep getting this 403 error response on the transfer page: Google Drive API has not been used in project xxxxx before or it is disabled. Enable it by visiting <url>

However, I've got the scope from the instructions enabled?
image

Also, when loading the app when the token has expired, a TON of popups try to open, causing the browser to block them (the message I saw said the browser blocked 12 pop-ups) That seems less than ideal

@bstrackany
Copy link
Contributor Author

Heya! Thanks for checking this out. Some thoughts:

  • I'll check the linting and address those
  • Yeah, you will get a bunch of 403 errors if there's an issue with the client id/GIS config. I'm not saying that's what's happening, but that's one of the possibilities. I can add some error checking so that if there's an issue with the client ID it doesn't keep trying more stuff.
  • I can also share screenshots of the client id I set up if that's helpful, I agree my instructions could be better -- I'll maybe make a new client ID on my own and that way the instructions will be solid.
  • For the tons of popups, which page were you on, was it the transfer page, or the connection panel? In theory the only time it'll do popups is when it's trying to get a new token which would only be the transfer page or the connection panel. I think what I can do is add some more error handling on the transfer page so that if the token has expired, it doesn't keep trying, and instead gives the user a "Refresh Google Drive Connection" button or something.

@bstrackany
Copy link
Contributor Author

OK, I simplified some changes and made it so that it doesn't show the transfer button or let you try to transfer if the Google Drive token has expired. So the user shouldn't face a bunch of forced popups. I also attached a PDF with a step by step of setting up the Client ID, I just ran through it and it works, so that should help.

I'll work on linting next.

@bstrackany
Copy link
Contributor Author

Ok linting addressed, and more error-handling in place. Want to try it again? Now the user can't get to the transfer page if they aren't connected.

One thing I didn't do is add error-handing if there a Google Client ID config issue, but I was reluctant to dive into that part of the transfer page. Maybe some try/catch blocks there, lmk.

@bstrackany
Copy link
Contributor Author

bstrackany commented Dec 17, 2025

In terms of expected user flow, the way I set it up is the whole application still uses local storage like it always did, I didn't touch anything there. The google drive piece only comes into play in the transfer page (and the google settings panel). So you can transfer your local storage to and from google drive.

That way you can be on multiple devices and connect to Google Drive with them (using the same email) and you'll be able to share data across by using the transfer page to upload/download data to/from google drive.

Once it all seemed to be working, I thought a future PR could add some QoL if needed.

@bstrackany
Copy link
Contributor Author

I also noticed npm install added a bunch of peer deps to the package.lock, I can roll that file back if it should be unchanged. Lmk.

@andyaiken
Copy link
Owner

It should be unchanged unless you're intentionally adding new dependencies.

@bstrackany
Copy link
Contributor Author

Lock file changes rolled back

@Veritas1000
Copy link
Contributor

I think I'm misunderstanding the intended user experience, because when I connect to the Drive API, I still just see all of my local stuff - does this feature just provide a manual backup/sync option, and not change where the live data (what the user sees when using the app) is coming from?

@bstrackany
Copy link
Contributor Author

bstrackany commented Dec 21, 2025

Hi @Veritas1000 the feature works the same way that the warehouse transfer feature does, same data paradigm. I wrote the Google drive as an optional drop in for the warehouse.

The main app uses local storage, that part is unchanged.

The way the Google drive piece works is on the transfer page, you can transfer your local data to and from Google drive. So it's basically being able to back up and restore your local hero and playbook data from Google drive.

The only user interactions currently are just in the feature flags, Google drive connection panel, and the transfer page. Everything else in the app is unchanged.

I could change it to not use the transfer page and just have "back to Google"/"restore backup from Google" buttons in the connection panel, but I was reluctant to step outside the merge/download paradigm established in the transfer page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Google drive syncing

3 participants