@@ -5,22 +5,85 @@ import logger, { LogId } from "../logger.js";
55import { ApiClient } from "../common/atlas/apiClient.js" ;
66import { MACHINE_METADATA } from "./constants.js" ;
77import { EventCache } from "./eventCache.js" ;
8+ import { createHmac } from "crypto" ;
9+ import { machineId } from "node-machine-id" ;
810
911type EventResult = {
1012 success : boolean ;
1113 error ?: Error ;
1214} ;
1315
1416export class Telemetry {
15- private readonly commonProperties : CommonProperties ;
17+ private isBufferingEvents : boolean = true ;
18+ private resolveDeviceId : ( deviceId : string ) => void = ( ) => { } ;
1619
17- constructor (
20+ private constructor (
1821 private readonly session : Session ,
19- private readonly eventCache : EventCache = EventCache . getInstance ( )
20- ) {
21- this . commonProperties = {
22- ...MACHINE_METADATA ,
23- } ;
22+ private readonly commonProperties : CommonProperties ,
23+ private readonly eventCache : EventCache
24+ ) { }
25+
26+ static create (
27+ session : Session ,
28+ commonProperties : CommonProperties = MACHINE_METADATA ,
29+ eventCache : EventCache = EventCache . getInstance ( )
30+ ) : Telemetry {
31+ const instance = new Telemetry ( session , commonProperties , eventCache ) ;
32+
33+ void instance . start ( ) ;
34+ return instance ;
35+ }
36+
37+ private async start ( ) : Promise < void > {
38+ this . commonProperties . device_id = await this . getDeviceId ( ) ;
39+
40+ this . isBufferingEvents = false ;
41+ await this . emitEvents ( this . eventCache . getEvents ( ) ) ;
42+ }
43+
44+ public async close ( ) : Promise < void > {
45+ this . resolveDeviceId ( "unknown" ) ;
46+ this . isBufferingEvents = false ;
47+ await this . emitEvents ( this . eventCache . getEvents ( ) ) ;
48+ }
49+
50+ private async machineIdWithTimeout ( ) : Promise < string > {
51+ try {
52+ return Promise . race < string > ( [
53+ machineId ( true ) ,
54+ new Promise < string > ( ( resolve , reject ) => {
55+ this . resolveDeviceId = resolve ;
56+ setTimeout ( ( ) => {
57+ reject ( new Error ( "Timeout getting machine ID" ) ) ;
58+ } , 3000 ) ;
59+ } ) ,
60+ ] ) ;
61+ } catch ( error ) {
62+ logger . debug ( LogId . telemetryMachineIdFailure , "telemetry" , `Error getting machine ID: ${ String ( error ) } ` ) ;
63+ return "unknown" ;
64+ }
65+ }
66+
67+ /**
68+ * @returns A hashed, unique identifier for the running device or `undefined` if not known.
69+ */
70+ private async getDeviceId ( ) : Promise < string > {
71+ if ( this . commonProperties . device_id ) {
72+ return this . commonProperties . device_id ;
73+ }
74+
75+ // Create a hashed format from the all uppercase version of the machine ID
76+ // to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.
77+
78+ const originalId = ( await this . machineIdWithTimeout ( ) ) . toUpperCase ( ) ;
79+
80+ const hmac = createHmac ( "sha256" , originalId ) ;
81+
82+ /** This matches the message used to create the hashes in Atlas CLI */
83+ const DEVICE_ID_HASH_MESSAGE = "atlascli" ;
84+
85+ hmac . update ( DEVICE_ID_HASH_MESSAGE ) ;
86+ return hmac . digest ( "hex" ) ;
2487 }
2588
2689 /**
@@ -84,6 +147,11 @@ export class Telemetry {
84147 * Falls back to caching if both attempts fail
85148 */
86149 private async emit ( events : BaseEvent [ ] ) : Promise < void > {
150+ if ( this . isBufferingEvents ) {
151+ this . eventCache . appendEvents ( events ) ;
152+ return ;
153+ }
154+
87155 const cachedEvents = this . eventCache . getEvents ( ) ;
88156 const allEvents = [ ...cachedEvents , ...events ] ;
89157
0 commit comments