The Drive module provides optional, user-initiated backup to Google Drive. Nothing is uploaded automatically — every network operation requires the user to interact with the History sidebar’s backup control. The implementation is split across two files:Documentation Index
Fetch the complete documentation index at: https://mintlify.com/arverma/Bihar-Police-Notebook/llms.txt
Use this file to discover all available pages before exploring further.
editor/js/drive-auth.js manages OAuth tokens, and editor/js/drive-sync.js handles folder creation, file upload/download, and merge logic.
Bihar Police Notebook requests only the
https://www.googleapis.com/auth/drive.file scope, which limits access to files and folders the app itself creates. It cannot read any other files in the user’s Drive.drive-auth.js
Token Lifecycle
Google Identity Services (GIS) is loaded lazily on first use. Access tokens are short-lived (approximately one hour); the auth module caches them in a dedicated IndexedDB store so they survive page reloads without requiring a new OAuth prompt.| Constant | Value |
|---|---|
| Auth DB name | bp-writing-tool-auth |
| Auth DB version | 1 |
| Auth store | session |
TOKEN_RETENTION_MS | 86 400 000 (24 hours) |
retainedUntil timestamp controls how long the IndexedDB session record is kept. If the current time exceeds retainedUntil, the record is discarded and the next operation will require a fresh OAuth prompt. Google’s own expiry (~1 h) is always respected as the inner bound.
Auth Functions
initDriveAuth()
https://accounts.google.com/gsi/client script once and creates the token client singleton. Hydrates any cached token from IndexedDB. Safe to call multiple times — subsequent calls return the cached initialisation promise.
connectDrive()
prompt: 'consent' on first connect so the Drive scope is approved). After a successful token response, fetches the user’s email from the Drive API and persists it to localStorage. Notifies all onAuthChange subscribers.
disconnectDrive()
google.accounts.oauth2.revoke, clears the in-memory token, deletes the IndexedDB session record, and removes the bpnt.drive.connected and bpnt.drive.email prefs. Does not delete the backup folder or any files from Google Drive.
requestAccessToken(opts?)
interactive is false and no valid cached token exists, the function still attempts a silent token request via GIS; if GIS requires user interaction it will throw. Throws on any OAuth error.
ensureAccessToken(opts?)
null if the user is not connected or if the token cannot be obtained without interaction when allowInteractive is false. Attempts a silent refresh first, then falls back to an interactive prompt only when allowInteractive: true. Called by authHeaders() and the sync module before every API call.
hasUsableAccessToken()
true if a non-expired token is available in memory or IndexedDB without making any network request. Used by the Drive chrome in main.js to decide whether to show the connected or disconnected UI state.
isConnected()
bpnt.drive.connected pref from localStorage. Does not probe token freshness; use hasUsableAccessToken() when you need to know if the token is still valid.
getConnectedEmail()
localStorage, or an empty string if not connected.
authHeaders()
{ Authorization: "Bearer {token}" } for use in fetch requests. Calls ensureAccessToken({ allowInteractive: false }) internally. Throws "Not connected to Google Drive" if no valid token is available.
onAuthChange(fn)
main.js uses this to update the Drive badge and reload the History list.
invalidateToken()
bpnt.drive.connected pref. Called by drive-sync.js after receiving a 401 response from the Drive API — the next API call will then attempt a silent token refresh.
drive-sync.js
Sync State Machine
The sync module maintains a global state observable viagetSyncState():
| State | Meaning |
|---|---|
idle | No operation running; last operation succeeded (or never ran) |
syncing | An enqueued operation is in progress |
error | Last operation failed; error field contains the message |
syncAll, pushPending, pullAndMerge, and ensureFolder — are serialised through a promise chain (enqueue). This prevents race conditions when the user clicks the backup button twice rapidly.
Sync Functions
syncAll()
pullAndMerge() followed by pushPending(). Returns a combined result object. If the user is not connected (isConnected() is false), returns { ok: false, reason: 'disconnected' } immediately.
pushPending(type?)
needsBackup returns true. When type is omitted, pushes both letters and diaries. For each document:
- Resolves the Drive file ID from
driveFileIdor by queryingappProperties.uuid. - Uploads via multipart create (new files) or media PATCH (existing files).
- Calls
markSyncedwith the newdriveFileIdandsyncedAt. - Hard-deletes local tombstones (
deletedAtset) after the deletion is confirmed uploaded.
syncError and continues. Returns ok: false only if every document failed.
pullAndMerge()
- If no local match exists by
uuid, inserts it viaupsertFromRemote. - If a local match exists and the remote
updated_atis newer, overwrites the local row. - If the local
updated_atis equal or newer, leaves the local row unchanged but records thedriveFileIdif not already set.
syncedAt ≥ deletedAt (fully synced deletions).
Merge strategy: last-write-wins by updated_at ISO string comparison.
ensureFolder()
Bihar Police Notebook Backup — do not delete) exists in My Drive. Checks the cached folder ID from localStorage first. If the folder was deleted or trashed:
- Clears the cached ID.
- Calls
clearAllDriveFileIds()so the next push re-creates all remote files. - Searches Drive for an existing folder with the correct name before creating a new one.
onSyncStatusChange(fn)
syncState transitions. main.js uses this to update the Drive badge spinner and the History list’s sync badges.
getSyncState()
Drive File Layout
Each document is stored as a single JSON file in the backup folder:appProperties.uuid and appProperties.type metadata so the sync module can match files back to local documents even if the local driveFileId was lost (e.g. after a browser data clear).