1+ import * as vscode from "vscode"
12import {
23 TelemetryEventName ,
34 type TelemetryEvent ,
@@ -9,8 +10,13 @@ import { BaseTelemetryClient } from "@roo-code/telemetry"
910import { getRooCodeApiUrl } from "./Config"
1011import type { AuthService } from "./auth"
1112import type { SettingsService } from "./SettingsService"
13+ import { TelemetryQueue } from "./TelemetryQueue"
1214
1315export class TelemetryClient extends BaseTelemetryClient {
16+ private telemetryQueue : TelemetryQueue | null = null
17+ private context : vscode . ExtensionContext | null = null
18+ private isOnline = true
19+
1420 constructor (
1521 private authService : AuthService ,
1622 private settingsService : SettingsService ,
@@ -25,27 +31,56 @@ export class TelemetryClient extends BaseTelemetryClient {
2531 )
2632 }
2733
28- private async fetch ( path : string , options : RequestInit ) {
34+ public setContext ( context : vscode . ExtensionContext ) : void {
35+ this . context = context
36+
37+ try {
38+ this . telemetryQueue = new TelemetryQueue ( context )
39+
40+ // Process any queued events on initialization
41+ this . processQueuedEvents ( )
42+ } catch ( error ) {
43+ console . error ( `Failed to initialize telemetry queue: ${ error } ` )
44+ // Continue without queue functionality
45+ this . telemetryQueue = null
46+ }
47+ }
48+
49+ private async fetch ( path : string , options : RequestInit ) : Promise < boolean > {
2950 if ( ! this . authService . isAuthenticated ( ) ) {
30- return
51+ return false
3152 }
3253
3354 const token = this . authService . getSessionToken ( )
3455
3556 if ( ! token ) {
3657 console . error ( `[TelemetryClient#fetch] Unauthorized: No session token available.` )
37- return
58+ return false
3859 }
3960
40- const response = await fetch ( `${ getRooCodeApiUrl ( ) } /api/${ path } ` , {
41- ...options ,
42- headers : { Authorization : `Bearer ${ token } ` , "Content-Type" : "application/json" } ,
43- } )
61+ try {
62+ const response = await fetch ( `${ getRooCodeApiUrl ( ) } /api/${ path } ` , {
63+ ...options ,
64+ headers : { Authorization : `Bearer ${ token } ` , "Content-Type" : "application/json" } ,
65+ } )
4466
45- if ( ! response . ok ) {
46- console . error (
47- `[TelemetryClient#fetch] ${ options . method } ${ path } -> ${ response . status } ${ response . statusText } ` ,
48- )
67+ const isSuccess = response . ok
68+
69+ if ( ! isSuccess ) {
70+ console . error (
71+ `[TelemetryClient#fetch] ${ options . method } ${ path } -> ${ response . status } ${ response . statusText } ` ,
72+ )
73+ }
74+
75+ // Update connection status based on response
76+ this . updateConnectionStatus ( isSuccess || response . status < 500 )
77+
78+ return isSuccess
79+ } catch ( error ) {
80+ // Network error - we're offline
81+ console . error ( `[TelemetryClient#fetch] Network error: ${ error } ` )
82+ this . updateConnectionStatus ( false )
83+ return false
4984 }
5085 }
5186
@@ -78,9 +113,19 @@ export class TelemetryClient extends BaseTelemetryClient {
78113 }
79114
80115 try {
81- await this . fetch ( `events` , { method : "POST" , body : JSON . stringify ( result . data ) } )
116+ const success = await this . fetch ( `events` , { method : "POST" , body : JSON . stringify ( result . data ) } )
117+
118+ if ( ! success && this . telemetryQueue ) {
119+ // Failed to send, add to queue
120+ await this . telemetryQueue . enqueue ( event )
121+ }
82122 } catch ( error ) {
83123 console . error ( `[TelemetryClient#capture] Error sending telemetry event: ${ error } ` )
124+
125+ // Add to queue on error
126+ if ( this . telemetryQueue ) {
127+ await this . telemetryQueue . enqueue ( event )
128+ }
84129 }
85130 }
86131
@@ -165,5 +210,67 @@ export class TelemetryClient extends BaseTelemetryClient {
165210 return true
166211 }
167212
168- public override async shutdown ( ) { }
213+ public override async shutdown ( ) {
214+ if ( this . telemetryQueue ) {
215+ this . telemetryQueue . dispose ( )
216+ }
217+ }
218+
219+ private updateConnectionStatus ( isOnline : boolean ) : void {
220+ this . isOnline = isOnline
221+
222+ if ( this . telemetryQueue ) {
223+ this . telemetryQueue . updateConnectionStatus ( isOnline )
224+
225+ // If we're back online, process queued events
226+ if ( isOnline ) {
227+ this . processQueuedEvents ( )
228+ }
229+ }
230+ }
231+
232+ private async processQueuedEvents ( ) : Promise < void > {
233+ if ( ! this . telemetryQueue ) {
234+ return
235+ }
236+
237+ await this . telemetryQueue . processQueue ( async ( event ) => {
238+ // Reuse the capture logic but send directly
239+ const payload = {
240+ type : event . event ,
241+ properties : await this . getEventProperties ( event ) ,
242+ }
243+
244+ const result = rooCodeTelemetryEventSchema . safeParse ( payload )
245+
246+ if ( ! result . success ) {
247+ // Invalid event, don't retry
248+ return true
249+ }
250+
251+ return await this . fetch ( `events` , { method : "POST" , body : JSON . stringify ( result . data ) } )
252+ } )
253+ }
254+
255+ public async checkConnection ( ) : Promise < void > {
256+ // Simple health check to update connection status
257+ try {
258+ const response = await fetch ( `${ getRooCodeApiUrl ( ) } /api/health` , {
259+ method : "GET" ,
260+ headers : { "Content-Type" : "application/json" } ,
261+ } )
262+
263+ this . updateConnectionStatus ( response . ok )
264+ } catch {
265+ this . updateConnectionStatus ( false )
266+ }
267+ }
268+
269+ public getConnectionStatus ( ) : "online" | "offline" {
270+ return this . telemetryQueue ?. getConnectionStatus ( ) || "online"
271+ }
272+
273+ public getQueueSize ( ) : number {
274+ return this . telemetryQueue ?. getQueueSize ( ) || 0
275+ }
169276}
0 commit comments