Skip to main content

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.

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: 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.
ConstantValue
Auth DB namebp-writing-tool-auth
Auth DB version1
Auth storesession
TOKEN_RETENTION_MS86 400 000 (24 hours)
The 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()

export async function initDriveAuth()
// Returns: Promise<void>
Initialises the GIS token client. Loads the 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()

export async function connectDrive()
// Returns: Promise<{ email: string }>
Starts an interactive OAuth flow (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()

export async function disconnectDrive()
// Returns: Promise<void>
Revokes the current access token via 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?)

export async function requestAccessToken(opts = {})
// opts: { interactive?: boolean }
// Returns: Promise<string>  — the access token
Requests a fresh access token from GIS. If a non-expired token is already cached it is returned immediately without a network call. When 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?)

export async function ensureAccessToken(opts = {})
// opts: { allowInteractive?: boolean }
// Returns: Promise<string | null>
Non-throwing version. Returns 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()

export async function hasUsableAccessToken()
// Returns: Promise<boolean>
Returns 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()

export function isConnected()
// Returns: boolean
Synchronous check — returns the value of the 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()

export function getConnectedEmail()
// Returns: string
Returns the connected Google account email from localStorage, or an empty string if not connected.

authHeaders()

export async function authHeaders()
// Returns: Promise<Record<string, string>>
Returns { 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)

export function onAuthChange(fn)
// fn: () => void
// Returns: () => void  — unsubscribe function
Registers a callback that fires whenever connection state changes (connect, disconnect, token refresh, email fetch). Returns an unsubscribe function. main.js uses this to update the Drive badge and reload the History list.

invalidateToken()

export function invalidateToken()
// Returns: void
Clears the in-memory token and queues deletion of the IndexedDB session record. Does not remove the 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 via getSyncState():
StateMeaning
idleNo operation running; last operation succeeded (or never ran)
syncingAn enqueued operation is in progress
errorLast operation failed; error field contains the message
All operations — 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.
// Serialisation via promise chain (drive-sync.js)
function enqueue(fn) {
  const run = queueTail.then(fn, fn);
  queueTail = run.then(() => {}, () => {});
  return run;
}

Sync Functions

syncAll()

export async function syncAll()
// Returns: Promise<{ ok: boolean, pull: object, push: object, error: string|null }>
Runs 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?)

export async function pushPending(type)
// type?: 'letter' | 'diary'
// Returns: Promise<{ ok: boolean, count?: number, error?: string }>
Uploads all documents for which needsBackup returns true. When type is omitted, pushes both letters and diaries. For each document:
  1. Resolves the Drive file ID from driveFileId or by querying appProperties.uuid.
  2. Uploads via multipart create (new files) or media PATCH (existing files).
  3. Calls markSynced with the new driveFileId and syncedAt.
  4. Hard-deletes local tombstones (deletedAt set) after the deletion is confirmed uploaded.
On partial failure, marks each failed document with syncError and continues. Returns ok: false only if every document failed.

pullAndMerge()

export async function pullAndMerge()
// Returns: Promise<{ ok: boolean, merged?: number, error?: string }>
Lists all JSON files in the backup folder and downloads each one. For every remote file:
  • If no local match exists by uuid, inserts it via upsertFromRemote.
  • If a local match exists and the remote updated_at is newer, overwrites the local row.
  • If the local updated_at is equal or newer, leaves the local row unchanged but records the driveFileId if not already set.
After merging, hard-deletes any local tombstones whose syncedAt ≥ deletedAt (fully synced deletions). Merge strategy: last-write-wins by updated_at ISO string comparison.

ensureFolder()

export async function ensureFolder()
// Returns: Promise<string>  — Drive folder ID
Ensures the backup folder (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:
  1. Clears the cached ID.
  2. Calls clearAllDriveFileIds() so the next push re-creates all remote files.
  3. Searches Drive for an existing folder with the correct name before creating a new one.

onSyncStatusChange(fn)

export function onSyncStatusChange(fn)
// fn: () => void
// Returns: () => void  — unsubscribe function
Registers a callback invoked whenever syncState transitions. main.js uses this to update the Drive badge spinner and the History list’s sync badges.

getSyncState()

export function getSyncState()
// Returns: { state: 'idle'|'syncing'|'error', error: string|null }
Returns the current sync state snapshot synchronously. Does not trigger any network activity.

Drive File Layout

Each document is stored as a single JSON file in the backup folder:
My Drive/
└── Bihar Police Notebook Backup — do not delete/
    ├── {uuid-1}.json
    ├── {uuid-2}.json
    └── ...
Every file carries 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).

JSON File Shape

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "type": "diary",
  "filename": "12 Jun 2024",
  "content": "{\"header\":{...},\"pages\":[...]}",
  "created_at": "2024-06-12T08:30:00.000Z",
  "updated_at": "2024-06-12T14:22:10.000Z",
  "deleted": false
}
The OAuth Web Client ID is stored in drive-config.js. You must add your development and production origins to the Authorised JavaScript origins list in the Google Cloud Console. Never commit a client secret to this repository — the Drive integration uses the implicit token flow (no secret required for browser apps).

Build docs developers (and LLMs) love