Google Drive home
The Drive home stores the encrypted backup in the user’s own Drive, not yours. You never operate storage, and Google never sees cleartext: the file is encrypted before it leaves the device, so the cloud holds opaque bytes.
One call
import { gisDriveAuth } from 'selfstore';
const outcome = await store.connectDrive(gisDriveAuth({ clientId: GOOGLE_CLIENT_ID }));
// 'started' | 'merged' | 'manual' | 'cancelled' (see quick start)
One prerequisite: an OAuth client id from a Google Cloud project (APIs and
Services, then Credentials, then “OAuth client ID”, type Web application, your
origin listed). selfstore requests only the drive.file scope, which grants
access solely to files this app created, the narrowest scope Drive offers.
The options it takes
interface GisDriveAuthOptions {
clientId: string;
scope?: string;
hint?: string | (() => string | null | undefined);
persist?: GisTokenPersistence; // 'memory' (default) | 'session'
}
hint pre-selects an account in the Google chooser - pass a function when your
app only learns which account later. persist: 'session' keeps the token for
the tab so a reload does not re-prompt; 'memory' drops it, which is the safer
default and the one a shared machine wants.
driveTarget.account() answers who holds the backup, as a
DriveAccount { email, name }. Worth showing next to “saved to Google Drive”: a
brand name is not an address, and several accounts look alike.
If the Drive file is encrypted, pass the password so the merge can read it:
store.connectDrive(auth, { password }). Without it the call throws
PASSWORD_REQUIRED before changing anything, so you can prompt and retry.
The honest trade-off
Stated plainly, because it shapes your UX:
- Client-only (
gisDriveAuth): zero backend, periodic re-consent. Google Identity Services issues roughly hour-long tokens to pure browser apps, and third-party-cookie rules prevent silent renewal forever. Expect an occasional one-click re-consent. Fine for a backup that syncs a few times a day. - Permanent connection: a small token broker. For “connected for good”,
exchange the OAuth code server-side for a refresh token and mint short-lived
access tokens on demand. That is the one place a tiny backend buys real UX,
and it still never sees data.
DriveAuthis three methods, so a broker slots in:
import type { DriveAuth } from 'selfstore';
const brokerAuth: DriveAuth = {
async token({ signal } = {}) { /* fetch a fresh access token; pass signal to your fetch */ },
async reconnect() { /* run consent again; true if access re-established */ },
async forget() { /* drop the session, locally and broker-side */ },
};
await store.connectDrive(brokerAuth);
Either way, a genuine loss of access (a real 401) surfaces as
store.status.action === 'reconnect'; a cold start or a blip is retried
silently and never bothers the user.
token() also receives a signal (since 1.1.0) that fires the moment the
user disconnects the backup. A broker running its own retry loop should pass
it to its fetches and stop retrying at once, so a “disconnect” tap lands
immediately instead of waiting for a slow request to time out. Ignoring the
signal keeps working exactly as before - the loop just runs to its own
deadline first.
On the advanced store
Driving createLocalStore yourself, the home is
driveTarget from the advanced subpath, which also ships shared-file
primitives (preview, adopt, findOrCreateOwnFile) for pointing several
devices at one file:
import { driveTarget, gisDriveAuth } from 'selfstore/advanced';
const drive = () => driveTarget.connect({ auth, kv: cache.kv, fileName: 'my-app.zip' });
await store.attachTarget(await drive(), { strategy: 'merge', password });
Sharing with other people
Handing a copy to somebody else is a different file, never the backup itself:
createCompanion mints one next to it, share publishes it on a link,
unshare takes that back, owner says whose Drive a copy lives on, and
secondary is the read-write target a mirror publishes into. The full reference
is in the advanced API;
the topology those calls serve is peers.
One thing they deliberately do not do: read another account’s shared file.
The drive.file scope only sees files your app created or the user picked, so
reaching somebody else’s copy needs either the Google Picker (a gesture per
file) or a relay you host - and a relay is a server, which is not a decision a
zero-server library gets to make for you.