feat(telegram): sync client display prefs via CloudStorage
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s

Theme, reduce-motion and board labels lived only in local IndexedDB/localStorage,
so they did not follow the user across devices and could be lost when the Telegram
WebView cleared storage.

Mirror these three device-independent prefs to Telegram CloudStorage (Bot API 6.9)
from the single local-persist point (persistPrefs), and reconcile them from
CloudStorage in the background on launch (reconcileCloudPrefs) so a change made on
another device follows the user here. The local store stays the instant-render
cache; the interface language is intentionally excluded (it syncs via the durable
account). Pure encode/decode extracted to cloudprefs.ts with unit tests; the
CloudStorage transport wrappers are added to telegram.ts. No-op outside Telegram or
on a client predating CloudStorage.
This commit is contained in:
Ilia Denisov
2026-06-24 11:39:29 +02:00
parent ea931c6680
commit 8a5a5d6c4d
4 changed files with 159 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
// Telegram CloudStorage sync for the device-independent client display preferences — theme,
// reduce-motion and board labels — so they follow the user across their Telegram devices. The
// interface language is intentionally excluded: it has its own server-side sync
// (Profile.preferredLanguage) plus an on-launch reconciler, and mixing it in here would fight that.
// The pure encode/decode is kept free of the SDK and the DOM so it unit-tests in the node
// environment; the CloudStorage transport wrappers live in telegram.ts and the wiring (mirror on
// save, reconcile on launch) in app.svelte.ts.
import type { ThemePref } from './theme';
import type { BoardLabelMode } from './boardlabels';
/** ClientPrefs is the subset of preferences synced across devices via Telegram CloudStorage. */
export interface ClientPrefs {
theme: ThemePref;
reduceMotion: boolean;
boardLabels: BoardLabelMode;
}
/** CLOUD_PREFS_KEY is the Telegram CloudStorage key holding the JSON-encoded ClientPrefs. */
export const CLOUD_PREFS_KEY = 'prefs';
/** encodeClientPrefs serialises the synced client prefs (and only those — never the locale). */
export function encodeClientPrefs(p: ClientPrefs): string {
return JSON.stringify({ theme: p.theme, reduceMotion: p.reduceMotion, boardLabels: p.boardLabels });
}
/**
* decodeClientPrefs parses a CloudStorage payload into a partial ClientPrefs, keeping only valid
* fields and dropping anything unknown, mistyped or malformed — so a value written by a newer or
* older build, or a corrupt entry, never throws and never applies a bad setting. A missing field
* stays absent, so the caller leaves the corresponding local value untouched.
*/
export function decodeClientPrefs(raw: string | null | undefined): Partial<ClientPrefs> {
if (!raw) return {};
let o: Record<string, unknown>;
try {
o = JSON.parse(raw) as Record<string, unknown>;
} catch {
return {};
}
if (!o || typeof o !== 'object') return {};
const out: Partial<ClientPrefs> = {};
if (o.theme === 'auto' || o.theme === 'light' || o.theme === 'dark') out.theme = o.theme;
if (typeof o.reduceMotion === 'boolean') out.reduceMotion = o.reduceMotion;
if (o.boardLabels === 'beginner' || o.boardLabels === 'classic' || o.boardLabels === 'none') {
out.boardLabels = o.boardLabels;
}
return out;
}