zudo-text

検索したい単語を入力

いつでも検索バーを開ける

Sync & Auth API

The Sync and Auth APIs manage cloud synchronization and user authentication through the backend bridge.

Cloud Sync API

The cloudSync namespace on BackendAPI provides methods to connect, sync, manage encryption, and listen for status or remote-change events. The namespace is backend.cloudSync.* — not backend.sync.*.

cloudSync.getStatus()

Get the current cloud sync status.

const status = await backend.cloudSync.getStatus();
// { status: "idle", lastSyncedAt: null, filesCount: 0, cursor: 0, connectedDevices: 0 }

Returns: CloudSyncStatusInfo — a snapshot of the current sync state.

cloudSync.triggerSync()

Trigger a manual cloud sync (push + pull).

const result = await backend.cloudSync.triggerSync();

Returns: CloudSyncStatusInfo — the sync state after the operation completes.

cloudSync.connect()

Connect the WebSocket for real-time sync notifications.

await backend.cloudSync.connect();

Returns: Promise<void>

cloudSync.disconnect()

Disconnect the WebSocket.

await backend.cloudSync.disconnect();

Returns: Promise<void>

cloudSync.setupEncryption(password)

Set up encryption password for the workspace. Called once when the user first configures sync.

const ok = await backend.cloudSync.setupEncryption("my-password");
ParameterTypeDescription
passwordstringThe user's encryption passphrase

Returns: Promise<boolean>

cloudSync.verifyPassword(password)

Verify the user's encryption password against the stored verification hash.

const valid = await backend.cloudSync.verifyPassword("my-password");
ParameterTypeDescription
passwordstringThe passphrase to verify

Returns: Promise<boolean>

cloudSync.armEncryption(password)

Arm end-to-end encryption with a single password gesture, branching on the workspace's current encryption state:

  • Workspace has no encryption: derive a fresh key and claim the workspace (outcome: "created").

  • Workspace has encryption: verify the password before arming (outcome: "verified"); a wrong password returns outcome: "wrong-password" and overwrites nothing.

const result = await backend.cloudSync.armEncryption("my-password");
if (result.outcome === "created" || result.outcome === "verified") {
  // Encryption is now armed; runtime observers are notified automatically.
}
ParameterTypeDescription
passwordstringThe passphrase to arm with

Returns: Promise<ArmEncryptionResult>

cloudSync.getConflicts()

Get pending sync conflicts.

const conflicts = await backend.cloudSync.getConflicts();

Returns: Promise<CloudConflictItem[]>

cloudSync.resolveConflicts(resolutions)

Resolve pending conflicts by choosing a per-file strategy.

await backend.cloudSync.resolveConflicts(
  new Map([
    ["path/to/file.md", "use-local"],
    ["path/to/other.md", "use-server"],
  ])
);
ParameterTypeDescription
resolutionsMap<string, "use-local" | "use-server" | "save-both">Per-file resolution strategy

Returns: Promise<void>

cloudSync.onStatusChanged(callback)

Listen for cloud sync status changes.

const unsubscribe = backend.cloudSync.onStatusChanged((status) => {
  console.log("Sync status:", status.status);
});

// Later: stop listening
unsubscribe();
ParameterTypeDescription
callback(status: CloudSyncStatusInfo) => voidCalled whenever the sync status changes

Returns: () => void — an unsubscribe function.

cloudSync.onRemoteChange(callback)

Listen for real-time remote change notifications pushed over the WebSocket.

const unsubscribe = backend.cloudSync.onRemoteChange((changes) => {
  console.log("Remote changes:", changes.length);
});

// Later: stop listening
unsubscribe();
ParameterTypeDescription
callback(changes: CloudConflictItem[]) => voidCalled when the server pushes change notifications

Returns: () => void — an unsubscribe function.

CloudSyncStatusType

type CloudSyncStatusType = "idle" | "syncing" | "synced" | "error" | "conflict" | "offline";
ValueDescription
"idle"No sync in progress, no previous sync
"syncing"Sync operation is currently running
"synced"Last sync completed successfully
"error"Last sync failed
"conflict"Sync found conflicting changes
"offline"Device is offline; sync is paused

CloudSyncStatusInfo

interface CloudSyncStatusInfo {
  status: CloudSyncStatusType;
  lastSyncedAt: string | null;
  filesCount: number;
  cursor: number;
  connectedDevices: number;
  error?: string;
}
FieldTypeDescription
statusCloudSyncStatusTypeCurrent sync state
lastSyncedAtstring | nullISO 8601 timestamp of last successful sync, or null if never synced
filesCountnumberNumber of files included in the last sync
cursornumberServer-side change log cursor position
connectedDevicesnumberNumber of devices currently connected to the workspace
errorstring (optional)Error message when status is "error"

defaultCloudSyncStatus

const defaultCloudSyncStatus: CloudSyncStatusInfo = {
  status: "idle",
  lastSyncedAt: null,
  filesCount: 0,
  cursor: 0,
  connectedDevices: 0,
};

ArmEncryptionResult

type ArmEncryptionResult =
  | { outcome: "created"; saltHex: string }
  | { outcome: "verified"; saltHex: string }
  | { outcome: "wrong-password" }
  | { outcome: "error"; message: string };
OutcomeDescription
"created"No prior encryption; a fresh key was derived and the workspace is now armed
"verified"Workspace already had encryption; password matched and keys are armed
"wrong-password"Workspace already had encryption but the password did NOT match — nothing was overwritten
"error"A prerequisite was missing or a network/derivation failure occurred; message explains

saltHex (present on created and verified) is the hex-encoded derivation salt that was actually used. Callers should persist this via persistWorkspaceKey(password, salt) for cold-start re-arm.

CloudConflictItem

interface CloudConflictItem {
  path: string;
  encryptedPath: string;
  localVersion: number;
  serverVersion: number;
  type: "concurrent-edit" | "edit-delete" | "delete-edit";
}

Auth API

The auth namespace on BackendAPI manages user authentication state — login, logout, and state change events. Better Auth uses a browser handoff and single-use OTT to establish the client session.

auth.getState()

Get the current authentication state.

const state = await backend.auth.getState();
// { isAuthenticated: false, user: null }

Returns: AuthState — the current auth state.

auth.login()

Start the Better Auth login flow. Desktop opens the system-browser handoff with the current app's deep-link scheme; the web adapter redirects to the allowlisted HTTPS handoff.

await backend.auth.login();

Returns: void

auth.logout()

Logout and clear tokens.

await backend.auth.logout();

Returns: void

auth.onStateChanged(callback)

Listen for authentication state changes.

const unsubscribe = backend.auth.onStateChanged((state) => {
  if (state.isAuthenticated) {
    console.log("Logged in as", state.user?.name);
  }
});

// Later: stop listening
unsubscribe();
ParameterTypeDescription
callback(state: AuthState) => voidCalled whenever the auth state changes

Returns: () => void — an unsubscribe function.

AuthUser

interface AuthUser {
  id: string;
  email: string;
  name: string;
  picture?: string;
}
FieldTypeDescription
idstringUnique user identifier
emailstringUser's email address
namestringDisplay name
picturestring (optional)Profile picture URL

AuthState

interface AuthState {
  isAuthenticated: boolean;
  user: AuthUser | null;
}
FieldTypeDescription
isAuthenticatedbooleanWhether the user is currently logged in
userAuthUser | nullThe authenticated user, or null when logged out

defaultAuthState

const defaultAuthState: AuthState = {
  isAuthenticated: false,
  user: null,
};

Adapter Implementations

MockAdapter

The mock adapter provides a fully functional in-memory implementation for tests and Storybook.

Cloud sync behavior:

  • getStatus() returns a clone of the internal cloudSyncStatus state

  • triggerSync() transitions through "syncing""synced" with a 500ms delay, counting all files (messages + inbox notes)

  • connect() / disconnect() are no-ops that resolve immediately

  • setupEncryption() / verifyPassword() / armEncryption() are in-memory stubs

  • getConflicts() returns an empty array by default

  • onStatusChanged() fires whenever triggerSync() completes or controls.setCloudSyncStatus() is called

  • onRemoteChange() fires when controls.triggerRemoteChange() is called

Auth behavior:

  • login() simulates a 500ms delay then sets a mock user (id: "mock-user-001", email: "user@example.com", name: "Mock User")

  • logout() resets to defaultAuthState

  • onStateChanged() fires on login and logout

TauriAdapter

Fully implements cloudSync: connects to the sync server with a short-lived Better Auth service JWT, drives push/pull cycles, manages the WebSocket via the @takazudo/cloud-sync client, and handles the per-app OTT deep-link flow. The adapter reads/writes encrypted blobs and persists the device cursor across sessions.

RestAdapter

Fully implements cloudSync: same contract as TauriAdapter, backed by HTTP/SSE calls to the sync server REST API. Used in pnpm dev:rest mode and on web (browser deploy). The Better Auth HTTPS handoff, service-JWT refresh hooks, and WebSocket connections are fully wired.