@@ -5,23 +5,101 @@ 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 nodeMachineId from "node-machine-id" ;
10+ import { DeferredPromise } from "../deferred-promise.js" ;
811
912type EventResult = {
1013 success : boolean ;
1114 error ?: Error ;
1215} ;
1316
17+ export const DEVICE_ID_TIMEOUT = 3000 ;
18+
1419export class Telemetry {
15- private readonly commonProperties : CommonProperties ;
20+ private isBufferingEvents : boolean = true ;
21+ /** Resolves when the device ID is retrieved or timeout occurs */
22+ public deviceIdPromise : DeferredPromise < string > | undefined ;
23+ private eventCache : EventCache ;
24+ private getRawMachineId : ( ) => Promise < string > ;
1625
17- constructor (
26+ private constructor (
1827 private readonly session : Session ,
1928 private readonly userConfig : UserConfig ,
20- private readonly eventCache : EventCache = EventCache . getInstance ( )
29+ private readonly commonProperties : CommonProperties ,
30+ { eventCache, getRawMachineId } : { eventCache : EventCache ; getRawMachineId : ( ) => Promise < string > }
2131 ) {
22- this . commonProperties = {
23- ...MACHINE_METADATA ,
24- } ;
32+ this . eventCache = eventCache ;
33+ this . getRawMachineId = getRawMachineId ;
34+ }
35+
36+ static create (
37+ session : Session ,
38+ userConfig : UserConfig ,
39+ {
40+ commonProperties = { ...MACHINE_METADATA } ,
41+ eventCache = EventCache . getInstance ( ) ,
42+
43+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
44+ getRawMachineId = ( ) => nodeMachineId . machineId ( true ) ,
45+ } : {
46+ eventCache ?: EventCache ;
47+ getRawMachineId ?: ( ) => Promise < string > ;
48+ commonProperties ?: CommonProperties ;
49+ } = { }
50+ ) : Telemetry {
51+ const instance = new Telemetry ( session , userConfig , commonProperties , { eventCache, getRawMachineId } ) ;
52+
53+ void instance . start ( ) ;
54+ return instance ;
55+ }
56+
57+ private async start ( ) : Promise < void > {
58+ if ( ! this . isTelemetryEnabled ( ) ) {
59+ return ;
60+ }
61+ this . deviceIdPromise = DeferredPromise . fromPromise ( this . getDeviceId ( ) , {
62+ timeout : DEVICE_ID_TIMEOUT ,
63+ onTimeout : ( resolve ) => {
64+ resolve ( "unknown" ) ;
65+ logger . debug ( LogId . telemetryDeviceIdTimeout , "telemetry" , "Device ID retrieval timed out" ) ;
66+ } ,
67+ } ) ;
68+ this . commonProperties . device_id = await this . deviceIdPromise ;
69+
70+ this . isBufferingEvents = false ;
71+ }
72+
73+ public async close ( ) : Promise < void > {
74+ this . deviceIdPromise ?. resolve ( "unknown" ) ;
75+ this . isBufferingEvents = false ;
76+ await this . emitEvents ( this . eventCache . getEvents ( ) ) ;
77+ }
78+
79+ /**
80+ * @returns A hashed, unique identifier for the running device or `"unknown"` if not known.
81+ */
82+ private async getDeviceId ( ) : Promise < string > {
83+ try {
84+ if ( this . commonProperties . device_id ) {
85+ return this . commonProperties . device_id ;
86+ }
87+
88+ const originalId : string = await this . getRawMachineId ( ) ;
89+
90+ // Create a hashed format from the all uppercase version of the machine ID
91+ // to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.
92+ const hmac = createHmac ( "sha256" , originalId . toUpperCase ( ) ) ;
93+
94+ /** This matches the message used to create the hashes in Atlas CLI */
95+ const DEVICE_ID_HASH_MESSAGE = "atlascli" ;
96+
97+ hmac . update ( DEVICE_ID_HASH_MESSAGE ) ;
98+ return hmac . digest ( "hex" ) ;
99+ } catch ( error ) {
100+ logger . debug ( LogId . telemetryDeviceIdFailure , "telemetry" , String ( error ) ) ;
101+ return "unknown" ;
102+ }
25103 }
26104
27105 /**
@@ -78,6 +156,11 @@ export class Telemetry {
78156 * Falls back to caching if both attempts fail
79157 */
80158 private async emit ( events : BaseEvent [ ] ) : Promise < void > {
159+ if ( this . isBufferingEvents ) {
160+ this . eventCache . appendEvents ( events ) ;
161+ return ;
162+ }
163+
81164 const cachedEvents = this . eventCache . getEvents ( ) ;
82165 const allEvents = [ ...cachedEvents , ...events ] ;
83166
0 commit comments