11import { getVersion } from "@tauri-apps/api/app" ;
22import { fetch as tauriFetch } from "@tauri-apps/plugin-http" ;
33import { arch , platform } from "@tauri-apps/plugin-os" ;
4+ import { load , type Store } from "@tauri-apps/plugin-store" ;
45import { getSettingsStore } from "@/features/settings/lib/settings-persistence" ;
56import { getApiBase } from "@/utils/api-base" ;
67
@@ -19,6 +20,16 @@ const STORE_KEY_LOG = "telemetry_log_v1";
1920const STORE_KEY_INSTALL_DATE = "telemetry_install_date" ;
2021const STORE_KEY_LAUNCH_COUNT = "telemetry_launch_count" ;
2122const STORE_KEY_LAST_APP_VERSION = "telemetry_last_app_version" ;
23+ const TELEMETRY_STORE_NAME = "telemetry.json" ;
24+ const TELEMETRY_STORE_KEYS = [
25+ STORE_KEY_DEVICE_ID ,
26+ STORE_KEY_LAST_HEARTBEAT ,
27+ STORE_KEY_QUEUE ,
28+ STORE_KEY_LOG ,
29+ STORE_KEY_INSTALL_DATE ,
30+ STORE_KEY_LAUNCH_COUNT ,
31+ STORE_KEY_LAST_APP_VERSION ,
32+ ] as const ;
2233
2334type TelemetryEventType =
2435 | "heartbeat"
@@ -67,12 +78,60 @@ let flushInFlight: Promise<boolean> | null = null;
6778let flushTimer : ReturnType < typeof setInterval > | null = null ;
6879let heartbeatTimer : ReturnType < typeof setInterval > | null = null ;
6980let listenersRegistered = false ;
81+ let telemetryStoreInstance : Store | null = null ;
82+ let telemetryStoreInitPromise : Promise < Store > | null = null ;
7083const logSubscribers = new Set < TelemetryLogSubscriber > ( ) ;
7184
7285function isRecord ( value : unknown ) : value is Record < string , unknown > {
7386 return typeof value === "object" && value !== null && ! Array . isArray ( value ) ;
7487}
7588
89+ async function migrateLegacyTelemetrySettingsStore ( telemetryStore : Store ) {
90+ const settingsStore = await getSettingsStore ( ) ;
91+ let settingsChanged = false ;
92+ let telemetryChanged = false ;
93+
94+ await Promise . all (
95+ TELEMETRY_STORE_KEYS . map ( async ( key ) => {
96+ const legacyValue = await settingsStore . get ( key ) ;
97+ if ( legacyValue === null || legacyValue === undefined ) return ;
98+
99+ const existingValue = await telemetryStore . get ( key ) ;
100+ if ( existingValue === null || existingValue === undefined ) {
101+ await telemetryStore . set ( key , legacyValue ) ;
102+ telemetryChanged = true ;
103+ }
104+
105+ await settingsStore . delete ( key ) ;
106+ settingsChanged = true ;
107+ } ) ,
108+ ) ;
109+
110+ await Promise . all ( [
111+ settingsChanged ? settingsStore . save ( ) : Promise . resolve ( ) ,
112+ telemetryChanged ? telemetryStore . save ( ) : Promise . resolve ( ) ,
113+ ] ) ;
114+ }
115+
116+ async function getTelemetryStore ( ) {
117+ if ( telemetryStoreInstance ) {
118+ return telemetryStoreInstance ;
119+ }
120+
121+ if ( ! telemetryStoreInitPromise ) {
122+ telemetryStoreInitPromise = ( async ( ) => {
123+ const store = await load ( TELEMETRY_STORE_NAME , {
124+ autoSave : true ,
125+ } as Parameters < typeof load > [ 1 ] ) ;
126+ await migrateLegacyTelemetrySettingsStore ( store ) ;
127+ telemetryStoreInstance = store ;
128+ return store ;
129+ } ) ( ) ;
130+ }
131+
132+ return telemetryStoreInitPromise ;
133+ }
134+
76135function serializeError ( error : unknown ) : { message : string ; stack ?: string } {
77136 if ( error instanceof Error ) {
78137 return {
@@ -137,7 +196,7 @@ function sanitizePayload(value: unknown): unknown {
137196}
138197
139198async function getOrCreateDeviceId ( ) {
140- const store = await getSettingsStore ( ) ;
199+ const store = await getTelemetryStore ( ) ;
141200 const existing = await store . get < string > ( STORE_KEY_DEVICE_ID ) ;
142201 if ( existing ) return existing ;
143202
@@ -148,9 +207,9 @@ async function getOrCreateDeviceId() {
148207}
149208
150209async function loadQueue (
151- storeArg ?: Awaited < ReturnType < typeof getSettingsStore > > ,
210+ storeArg ?: Awaited < ReturnType < typeof getTelemetryStore > > ,
152211) : Promise < QueuedTelemetryEvent [ ] > {
153- const store = storeArg ?? ( await getSettingsStore ( ) ) ;
212+ const store = storeArg ?? ( await getTelemetryStore ( ) ) ;
154213 const queue = await store . get < unknown > ( STORE_KEY_QUEUE ) ;
155214 if ( ! Array . isArray ( queue ) ) return [ ] ;
156215
@@ -167,17 +226,17 @@ async function loadQueue(
167226
168227async function saveQueue (
169228 queue : QueuedTelemetryEvent [ ] ,
170- storeArg ?: Awaited < ReturnType < typeof getSettingsStore > > ,
229+ storeArg ?: Awaited < ReturnType < typeof getTelemetryStore > > ,
171230) {
172- const store = storeArg ?? ( await getSettingsStore ( ) ) ;
231+ const store = storeArg ?? ( await getTelemetryStore ( ) ) ;
173232 await store . set ( STORE_KEY_QUEUE , queue ) ;
174233 await store . save ( ) ;
175234}
176235
177236async function loadLogEntries (
178- storeArg ?: Awaited < ReturnType < typeof getSettingsStore > > ,
237+ storeArg ?: Awaited < ReturnType < typeof getTelemetryStore > > ,
179238) : Promise < TelemetryLogEntry [ ] > {
180- const store = storeArg ?? ( await getSettingsStore ( ) ) ;
239+ const store = storeArg ?? ( await getTelemetryStore ( ) ) ;
181240 const log = await store . get < unknown > ( STORE_KEY_LOG ) ;
182241 if ( ! Array . isArray ( log ) ) return [ ] ;
183242
@@ -195,9 +254,9 @@ async function loadLogEntries(
195254
196255async function persistLogEntries (
197256 entries : TelemetryLogEntry [ ] ,
198- storeArg ?: Awaited < ReturnType < typeof getSettingsStore > > ,
257+ storeArg ?: Awaited < ReturnType < typeof getTelemetryStore > > ,
199258) {
200- const store = storeArg ?? ( await getSettingsStore ( ) ) ;
259+ const store = storeArg ?? ( await getTelemetryStore ( ) ) ;
201260 const limitedEntries = entries . slice ( - MAX_LOG_ENTRIES ) ;
202261 await store . set ( STORE_KEY_LOG , limitedEntries ) ;
203262 await store . save ( ) ;
@@ -212,9 +271,9 @@ function notifyLogSubscribers(entries: TelemetryLogEntry[]) {
212271
213272async function appendLogEntry (
214273 entry : Omit < TelemetryLogEntry , "id" | "timestamp" > ,
215- storeArg ?: Awaited < ReturnType < typeof getSettingsStore > > ,
274+ storeArg ?: Awaited < ReturnType < typeof getTelemetryStore > > ,
216275) {
217- const store = storeArg ?? ( await getSettingsStore ( ) ) ;
276+ const store = storeArg ?? ( await getTelemetryStore ( ) ) ;
218277 const entries = await loadLogEntries ( store ) ;
219278 entries . push ( {
220279 id : crypto . randomUUID ( ) ,
@@ -228,7 +287,7 @@ async function ensureClientContext(): Promise<TelemetryClientContext> {
228287 if ( clientContextPromise ) return clientContextPromise ;
229288
230289 clientContextPromise = ( async ( ) => {
231- const store = await getSettingsStore ( ) ;
290+ const store = await getTelemetryStore ( ) ;
232291 const [ deviceId , appVersion ] = await Promise . all ( [ getOrCreateDeviceId ( ) , getVersion ( ) ] ) ;
233292
234293 const nowIso = new Date ( ) . toISOString ( ) ;
@@ -267,14 +326,14 @@ async function isUsageTelemetryEnabled(): Promise<boolean> {
267326}
268327
269328async function shouldQueueHeartbeat ( ) : Promise < boolean > {
270- const store = await getSettingsStore ( ) ;
329+ const store = await getTelemetryStore ( ) ;
271330 const lastHeartbeat = await store . get < number > ( STORE_KEY_LAST_HEARTBEAT ) ;
272331 if ( ! lastHeartbeat ) return true ;
273332 return Date . now ( ) - lastHeartbeat >= HEARTBEAT_INTERVAL_MS ;
274333}
275334
276335async function markHeartbeatQueued ( ) {
277- const store = await getSettingsStore ( ) ;
336+ const store = await getTelemetryStore ( ) ;
278337 await store . set ( STORE_KEY_LAST_HEARTBEAT , Date . now ( ) ) ;
279338 await store . save ( ) ;
280339}
@@ -296,7 +355,7 @@ async function enqueueTelemetryEvent(
296355 const mode = options ?. mode ?? "optional" ;
297356 if ( mode === "optional" && ! ( await isUsageTelemetryEnabled ( ) ) ) return false ;
298357
299- const store = await getSettingsStore ( ) ;
358+ const store = await getTelemetryStore ( ) ;
300359 const context = await ensureClientContext ( ) ;
301360 const queue = await loadQueue ( store ) ;
302361
@@ -358,7 +417,7 @@ export async function flushTelemetryQueue(): Promise<boolean> {
358417 }
359418
360419 flushInFlight = ( async ( ) => {
361- const store = await getSettingsStore ( ) ;
420+ const store = await getTelemetryStore ( ) ;
362421 const queue = await loadQueue ( store ) ;
363422 if ( queue . length === 0 ) return true ;
364423
@@ -548,7 +607,7 @@ export async function getTelemetryLogEntries(): Promise<TelemetryLogEntry[]> {
548607}
549608
550609export async function clearTelemetryLogEntries ( ) {
551- const store = await getSettingsStore ( ) ;
610+ const store = await getTelemetryStore ( ) ;
552611 await persistLogEntries ( [ ] , store ) ;
553612 await appendLogEntry (
554613 {
@@ -573,6 +632,9 @@ export function initializeTelemetry(): Promise<void> {
573632 if ( initializationPromise ) return initializationPromise ;
574633
575634 registerCrashListeners ( ) ;
635+ void getTelemetryStore ( ) . catch ( ( error ) => {
636+ console . error ( "Telemetry store initialization failed:" , error ) ;
637+ } ) ;
576638 setTimeout ( ( ) => {
577639 void ensureClientContext ( )
578640 . then ( ( ) => queueHeartbeat ( ) )
0 commit comments