Skip to content

Sync providers ​

Desktop extensions can contribute file-sync backends under Settings ▸ Files ▸ Favorite Folders & Sync. The application owns scheduling, baselines, three-way merges, recovery copies and credentials. The extension handles remote file operations.

Use "tags": ["Data Sync"] in the manifest. While the extension is still in development, you can also set "unlisted": true to share it by direct URL without listing it in the store; see Unlisted extensions.

json
{
  "main": "main.js",
  "permissions": { "network": ["https://api.dropboxapi.com", "https://content.dropboxapi.com"] },
  "contributes": {
    "syncProviders": [{
      "id": "dropbox",
      "title": "Dropbox",
      "authorizeUrl": "https://www.dropbox.com/oauth2/authorize",
      "tokenUrl": "https://api.dropboxapi.com/oauth2/token",
      "scope": "files.metadata.read files.content.read files.content.write account_info.read",
      "extra": { "token_access_type": "offline" }
    }]
  }
}

Register a handler in the worker:

js
typora.sync.registerProvider("dropbox", async request => {
  // request: {op, root, path?, data?, version?, accessToken}
  // Call the remote API using typora.net.fetch; never log accessToken.
});
OperationResult
identifyStable account/root identity string
listAll direct children, with complete pagination: {path, directory, version, id?}[]
statChild metadata or null only for confirmed absence
read{data, version}, where data is base64 bytes associated with that exact version
mkdirCreate directory or accept existing directory; reject an existing file
writeNew version string; version: null requires create-only, otherwise conditional replace
removeConditional delete using supplied version

path is slash-separated and relative to the bound root. data is base64, including binary attachments. Report API errors as {error: {status, message, retryAfter}}; retryAfter is milliseconds. Never turn permission/transport failures into absence. The host checks create, stale update and stale delete semantics before enabling a binding.

The worker receives a short-lived access token and is subject to the extension's declared network grants. Refresh tokens remain in the host credential store. OAuth currently permits the official Dropbox and Google endpoint pairs only; supporting another issuer requires a host change. Only access_type, prompt, and token_access_type are accepted extra authorization parameters; state, redirect URI and PKCE are host-owned.

Reference implementations are typora.sync-dropbox and typora.sync-google-drive in the extensions repository. Both require a registered OAuth Client ID; no production credentials are bundled. Desktop loopback authorization is not an iOS authorization implementation.

File-tree sync indicators ​

Backends registered with syncProviders automatically receive per-file indicators from Typora's sync engine. Do not report an HTTP failure as a conflict: conflicts mean that the automatic merge could not reconcile the versions. A queued file is not necessarily transferring yet.

Extensions that manage synchronization independently can contribute a status provider. This does not grant permission to read files outside the open workspace, create placeholder files, or modify a document.

json
{
  "main": "main.js",
  "permissions": { "workspace": "read" },
  "contributes": {
    "fileSyncStatusProviders": [{ "id": "my-drive", "title": "My Drive" }]
  }
}
js
const registration = typora.sync.registerStatusProvider("my-drive", async paths => {
  // paths: absolute paths of visible files in the current workspace.
  // Return only known statuses, keyed by those same paths.
  const states = {};
  for (const path of paths) {
    const state = await mySyncEngine.status(path);
    if (["syncing", "conflict", "not-downloaded"].includes(state)) {
      states[path] = state;
    }
  }
  return states;
});

// Optional: invalidate visible status immediately after the engine changes.
typora.sync.notifyStatusChanged();
// When deactivating: registration.dispose();

The file tree queries visible files every two seconds while foregrounded, and on invalidation. Each result is a complete snapshot for the requested paths; omitted paths lose their previous marker. Unrequested paths and unknown values are ignored. Failed or timed-out queries do not leave a stale spinner. Disabling the extension unregisters its provider. Priority is conflict, then syncing, then not-downloaded; all other states have no marker. Labels and icons are host-owned. The extension is not given OAuth tokens through this status API.

This contribution decorates existing file entries; it does not add virtual files or implement downloading. Extensions needing remote directory entries and materialization should use syncProviders, so Typora owns the local cache.

Theme adapted from Nimbus (MIT).