@@ -2,7 +2,7 @@ import path from 'path';
2
2
import * as vscode from 'vscode' ;
3
3
import { config } from 'dotenv' ;
4
4
import type { DataService } from 'mongodb-data-service' ;
5
- import fs from 'fs' ;
5
+ import fs from 'fs/promises ' ;
6
6
import { Analytics as SegmentAnalytics } from '@segment/analytics-node' ;
7
7
import { throttle } from 'lodash' ;
8
8
@@ -18,6 +18,10 @@ import {
18
18
SidePanelOpenedTelemetryEvent ,
19
19
ParticipantResponseFailedTelemetryEvent ,
20
20
} from './telemetryEvents' ;
21
+ import { getDeviceId } from '@mongodb-js/device-id' ;
22
+
23
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
24
+ const nodeMachineId = require ( 'node-machine-id' ) ;
21
25
22
26
const log = createLogger ( 'telemetry' ) ;
23
27
// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -26,19 +30,25 @@ const { version } = require('../../package.json');
26
30
export type SegmentProperties = {
27
31
event : string ;
28
32
anonymousId : string ;
33
+ deviceId ?: string ;
29
34
properties : Record < string , any > ;
30
35
} ;
31
36
32
37
/**
33
38
* This controller manages telemetry.
34
39
*/
35
40
export class TelemetryService {
36
- _segmentAnalytics ?: SegmentAnalytics ;
37
- _segmentAnonymousId : string ;
38
- _segmentKey ?: string ; // The segment API write key.
39
-
40
- private _context : vscode . ExtensionContext ;
41
- private _shouldTrackTelemetry : boolean ; // When tests run the extension, we don't want to track telemetry.
41
+ private _segmentAnalytics ?: SegmentAnalytics ;
42
+ public _segmentKey ?: string ; // The segment API write key.
43
+ private eventBuffer : TelemetryEvent [ ] = [ ] ;
44
+ private isBufferingEvents = true ;
45
+ public userIdentity : {
46
+ anonymousId : string ;
47
+ deviceId ?: string ;
48
+ } ;
49
+ private resolveDeviceId : ( ( value : string ) => void ) | undefined ;
50
+ private readonly _context : vscode . ExtensionContext ;
51
+ private readonly _shouldTrackTelemetry : boolean ; // When tests run the extension, we don't want to track telemetry.
42
52
43
53
constructor (
44
54
storageController : StorageController ,
@@ -48,11 +58,12 @@ export class TelemetryService {
48
58
const { anonymousId } = storageController . getUserIdentity ( ) ;
49
59
this . _context = context ;
50
60
this . _shouldTrackTelemetry = shouldTrackTelemetry || false ;
51
- this . _segmentAnonymousId = anonymousId ;
52
- this . _segmentKey = this . _readSegmentKey ( ) ;
61
+ this . userIdentity = {
62
+ anonymousId,
63
+ } ;
53
64
}
54
65
55
- private _readSegmentKey ( ) : string | undefined {
66
+ private async readSegmentKey ( ) : Promise < string | undefined > {
56
67
config ( { path : path . join ( this . _context . extensionPath , '.env' ) } ) ;
57
68
58
69
try {
@@ -61,18 +72,21 @@ export class TelemetryService {
61
72
'./constants.json'
62
73
) ;
63
74
// eslint-disable-next-line no-sync
64
- const constantsFile = fs . readFileSync ( segmentKeyFileLocation , 'utf8' ) ;
75
+ const constantsFile = await fs . readFile ( segmentKeyFileLocation , {
76
+ encoding : 'utf8' ,
77
+ } ) ;
65
78
const { segmentKey } = JSON . parse ( constantsFile ) as {
66
79
segmentKey ?: string ;
67
80
} ;
68
81
return segmentKey ;
69
82
} catch ( error ) {
70
83
log . error ( 'Failed to read segmentKey from the constants file' , error ) ;
71
- return ;
84
+ return undefined ;
72
85
}
73
86
}
74
87
75
- activateSegmentAnalytics ( ) : void {
88
+ async activateSegmentAnalytics ( ) : Promise < void > {
89
+ this . _segmentKey = await this . readSegmentKey ( ) ;
76
90
if ( ! this . _segmentKey ) {
77
91
return ;
78
92
}
@@ -83,12 +97,20 @@ export class TelemetryService {
83
97
flushInterval : 10000 , // 10 seconds is the default libraries' value.
84
98
} ) ;
85
99
86
- const segmentProperties = this . getTelemetryUserIdentity ( ) ;
87
- this . _segmentAnalytics . identify ( segmentProperties ) ;
88
- log . info ( 'Segment analytics activated' , segmentProperties ) ;
100
+ this . userIdentity = await this . getTelemetryUserIdentity ( ) ;
101
+ this . _segmentAnalytics . identify ( this . userIdentity ) ;
102
+ this . isBufferingEvents = false ;
103
+ log . info ( 'Segment analytics activated' , this . userIdentity ) ;
104
+
105
+ // Process buffered events
106
+ let event : TelemetryEvent | undefined ;
107
+ while ( ( event = this . eventBuffer . shift ( ) ) ) {
108
+ this . track ( event ) ;
109
+ }
89
110
}
90
111
91
112
deactivate ( ) : void {
113
+ this . resolveDeviceId ?.( 'unknown' ) ;
92
114
// Flush on demand to make sure that nothing is left in the queue.
93
115
void this . _segmentAnalytics ?. closeAndFlush ( ) ;
94
116
}
@@ -130,8 +152,13 @@ export class TelemetryService {
130
152
131
153
track ( event : TelemetryEvent ) : void {
132
154
try {
155
+ if ( this . isBufferingEvents ) {
156
+ this . eventBuffer . push ( event ) ;
157
+ return ;
158
+ }
159
+
133
160
this . _segmentAnalyticsTrack ( {
134
- ...this . getTelemetryUserIdentity ( ) ,
161
+ ...this . userIdentity ,
135
162
event : event . type ,
136
163
properties : {
137
164
...event . properties ,
@@ -153,9 +180,17 @@ export class TelemetryService {
153
180
this . track ( new NewConnectionTelemetryEvent ( connectionTelemetryProperties ) ) ;
154
181
}
155
182
156
- getTelemetryUserIdentity ( ) : { anonymousId : string } {
183
+ private async getTelemetryUserIdentity ( ) : Promise < typeof this . userIdentity > {
184
+ const { value : deviceId , resolve : resolveDeviceId } = getDeviceId ( {
185
+ getMachineId : ( ) : Promise < string > => nodeMachineId . machineId ( true ) ,
186
+ isNodeMachineId : true ,
187
+ } ) ;
188
+
189
+ this . resolveDeviceId = resolveDeviceId ;
190
+
157
191
return {
158
- anonymousId : this . _segmentAnonymousId ,
192
+ anonymousId : this . userIdentity . anonymousId ,
193
+ deviceId : await deviceId ,
159
194
} ;
160
195
}
161
196
0 commit comments