@@ -2,7 +2,7 @@ import path from 'path';
22import * as vscode from 'vscode' ;
33import { config } from 'dotenv' ;
44import type { DataService } from 'mongodb-data-service' ;
5- import fs from 'fs' ;
5+ import fs from 'fs/promises ' ;
66import { Analytics as SegmentAnalytics } from '@segment/analytics-node' ;
77import { throttle } from 'lodash' ;
88
@@ -18,6 +18,10 @@ import {
1818 SidePanelOpenedTelemetryEvent ,
1919 ParticipantResponseFailedTelemetryEvent ,
2020} 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' ) ;
2125
2226const log = createLogger ( 'telemetry' ) ;
2327// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -26,19 +30,25 @@ const { version } = require('../../package.json');
2630export type SegmentProperties = {
2731 event : string ;
2832 anonymousId : string ;
33+ deviceId ?: string ;
2934 properties : Record < string , any > ;
3035} ;
3136
3237/**
3338 * This controller manages telemetry.
3439 */
3540export 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.
4252
4353 constructor (
4454 storageController : StorageController ,
@@ -48,11 +58,12 @@ export class TelemetryService {
4858 const { anonymousId } = storageController . getUserIdentity ( ) ;
4959 this . _context = context ;
5060 this . _shouldTrackTelemetry = shouldTrackTelemetry || false ;
51- this . _segmentAnonymousId = anonymousId ;
52- this . _segmentKey = this . _readSegmentKey ( ) ;
61+ this . userIdentity = {
62+ anonymousId,
63+ } ;
5364 }
5465
55- private _readSegmentKey ( ) : string | undefined {
66+ private async readSegmentKey ( ) : Promise < string | undefined > {
5667 config ( { path : path . join ( this . _context . extensionPath , '.env' ) } ) ;
5768
5869 try {
@@ -61,18 +72,21 @@ export class TelemetryService {
6172 './constants.json'
6273 ) ;
6374 // eslint-disable-next-line no-sync
64- const constantsFile = fs . readFileSync ( segmentKeyFileLocation , 'utf8' ) ;
75+ const constantsFile = await fs . readFile ( segmentKeyFileLocation , {
76+ encoding : 'utf8' ,
77+ } ) ;
6578 const { segmentKey } = JSON . parse ( constantsFile ) as {
6679 segmentKey ?: string ;
6780 } ;
6881 return segmentKey ;
6982 } catch ( error ) {
7083 log . error ( 'Failed to read segmentKey from the constants file' , error ) ;
71- return ;
84+ return undefined ;
7285 }
7386 }
7487
75- activateSegmentAnalytics ( ) : void {
88+ async activateSegmentAnalytics ( ) : Promise < void > {
89+ this . _segmentKey = await this . readSegmentKey ( ) ;
7690 if ( ! this . _segmentKey ) {
7791 return ;
7892 }
@@ -83,12 +97,20 @@ export class TelemetryService {
8397 flushInterval : 10000 , // 10 seconds is the default libraries' value.
8498 } ) ;
8599
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+ }
89110 }
90111
91112 deactivate ( ) : void {
113+ this . resolveDeviceId ?.( 'unknown' ) ;
92114 // Flush on demand to make sure that nothing is left in the queue.
93115 void this . _segmentAnalytics ?. closeAndFlush ( ) ;
94116 }
@@ -130,8 +152,13 @@ export class TelemetryService {
130152
131153 track ( event : TelemetryEvent ) : void {
132154 try {
155+ if ( this . isBufferingEvents ) {
156+ this . eventBuffer . push ( event ) ;
157+ return ;
158+ }
159+
133160 this . _segmentAnalyticsTrack ( {
134- ...this . getTelemetryUserIdentity ( ) ,
161+ ...this . userIdentity ,
135162 event : event . type ,
136163 properties : {
137164 ...event . properties ,
@@ -153,9 +180,17 @@ export class TelemetryService {
153180 this . track ( new NewConnectionTelemetryEvent ( connectionTelemetryProperties ) ) ;
154181 }
155182
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+
157191 return {
158- anonymousId : this . _segmentAnonymousId ,
192+ anonymousId : this . userIdentity . anonymousId ,
193+ deviceId : await deviceId ,
159194 } ;
160195 }
161196
0 commit comments