API token usage, R2 request patterns, encryption internals, and self-hosting the pairing relay. Useful if you are modifying, auditing, or extending the plugin.
SSS uses an S3-compatible API to interact with Cloudflare R2. The API token you create in Cloudflare is used directly — there is no intermediate server. Every request is made from the Obsidian client on your device.
A typical sync cycle issues the following R2 API calls:
| Operation | API call | When |
|---|---|---|
| List remote files | ListObjectsV2 | Every sync — one call (paginated for large vaults) |
| Download file | GetObject | Per file pulled from remote |
| Upload file | PutObject | Per file pushed to remote |
| Delete file | DeleteObject | Per deletion propagated to remote |
| Get remote ETag | HeadObject | Per pushed file (to store ETag in prevSync) |
| Sentinel read/write | GetObject / PutObject | Smart Sync only — one small object per sync |
| Resource | Free allowance | Typical vault usage |
|---|---|---|
| Storage | 10 GB / month | Most vaults < 500 MB |
| Class A operations (writes, lists) | 1,000,000 / month | Low — only changed files are written |
| Class B operations (reads) | 10,000,000 / month | Very low with Smart Sync (sentinel-driven) |
ListObjectsV2 calls.SSS supports two encryption methods, selectable per-vault. Both use client-side encryption — files are encrypted before upload and decrypted after download.
openssl-base64)Salted__ header convention.rclone-base64)rclone for manual recovery using the same password.The sync engine compares three states for each file path:
Current state of the file on this device's vault.
Current state of the encrypted blob in R2.
Snapshot from the last successful sync, stored in a local SQLite DB.
Change detection uses ETag for remote comparisons and mtime + size for local comparisons. ETags are stored in the prevSync record after each push to avoid re-uploading unchanged encrypted files (whose size and mtime differ from plaintext).
| Local changed? | Remote changed? | Decision |
|---|---|---|
| No | No | Skip (no_change) |
| Yes | No | Push local → remote |
| No | Yes | Pull remote → local |
| Yes | Yes | Conflict — resolved per setting (keep_newer, keep_larger, etc.) |
| Deleted locally | Unchanged | Delete remote |
| Unchanged | Deleted remotely | Delete local |
| New (no prevSync) | Absent | Push (first upload) |
| Absent | New (no prevSync) | Pull (first download) |
Smart Sync uses a lightweight sentinel object in R2 (.sss-sentinel) to notify other devices of a completed sync without requiring a persistent WebSocket connection.
After smartSyncIdleSeconds (default 7 s) of editor inactivity, a sync is triggered. This is debounced on every editor-change event.
After a successful sync, the plugin writes a small JSON object to R2 containing a timestamp and device ID. Sentinel writes are skipped for state_aware triggers to prevent A→B→A cascade loops.
Other open devices poll the sentinel every 30 s. If the sentinel's timestamp is newer than the last seen value and the device ID differs (i.e., another device wrote it), a state_aware sync is triggered to pull changes.
The poll interval is paused when the document is hidden (app backgrounded) and resumed immediately on visibility. A one-shot poll fires on foreground to catch missed changes without waiting 30 s.
The pairing relay is a minimal Cloudflare Worker that holds an encrypted credential bundle for up to 10 minutes. It is fully open source.
Repository: github.com/xensenx/sss-relay
Deploy it as a Cloudflare Worker, then point SSS to your URL under Settings → Advanced → Custom Relay URL. The relay never sees plaintext credentials — the bundle is encrypted client-side with a key derived from the pairing code before being sent.
| Endpoint | Method | Purpose |
|---|---|---|
/create | POST | Store encrypted bundle, receive pairing code |
/consume/:code | POST | Retrieve and delete bundle by code |
/health | GET | Relay liveness check |
SSS uses IndexedDB (via the idb-keyval-style abstraction in database.ts) to persist sync state. The database is namespaced by vault ID so multiple vaults on the same device do not interfere.
Key tables:
prev_sync_records — one row per synced file: path, size, mtime, ETag.sync_history — timestamped log of past sync stats for diagnostics.meta — plugin version, last success/failure timestamps.The database location is managed by Obsidian's plugin data API and is not directly accessible from the filesystem. Use Reset Sync History in Danger Zone to clear prev_sync_records, which forces a full comparison on the next sync.
# Clone the repository
git clone https://github.com/xensenx/Secure_Smart_Sync.git
cd Secure_Smart_Sync
# Install dependencies
npm install
# Development build (watch mode)
npm run dev
# Production build
npm run build
The built output (main.js, manifest.json, styles.css) is placed in the project root. Copy these into your vault's .obsidian/plugins/Secure-Smart-Sync/ directory to test.