Skip to content

Commit 642243f

Browse files
authored
chore: switch to @mongodb-js/device-id (#2446)
1 parent 34f7a4d commit 642243f

File tree

4 files changed

+86
-61
lines changed

4 files changed

+86
-61
lines changed

package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/logging/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"node": ">=14.15.1"
1818
},
1919
"dependencies": {
20+
"@mongodb-js/device-id": "^0.2.1",
2021
"@mongodb-js/devtools-connect": "^3.4.1",
2122
"@mongosh/errors": "2.4.0",
2223
"@mongosh/history": "2.4.6",
@@ -29,6 +30,7 @@
2930
"@mongodb-js/eslint-config-mongosh": "^1.0.0",
3031
"@mongodb-js/prettier-config-devtools": "^1.0.1",
3132
"@mongodb-js/tsconfig-mongosh": "^1.0.0",
33+
"@segment/analytics-node": "^1.3.0",
3234
"depcheck": "^1.4.7",
3335
"eslint": "^7.25.0",
3436
"prettier": "^2.8.8",

packages/logging/src/logging-and-telemetry.spec.ts

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import type { Writable } from 'stream';
88
import type { MongoshLoggingAndTelemetry } from '.';
99
import { setupLoggingAndTelemetry } from '.';
1010
import type { LoggingAndTelemetry } from './logging-and-telemetry';
11-
import { getDeviceId } from './logging-and-telemetry';
1211
import sinon from 'sinon';
1312
import type { MongoshLoggingAndTelemetryArguments } from './types';
13+
import { getDeviceId } from '@mongodb-js/device-id';
14+
import { getMachineId } from 'native-machine-id';
1415

1516
describe('MongoshLoggingAndTelemetry', function () {
1617
let logOutput: any[];
@@ -253,6 +254,7 @@ describe('MongoshLoggingAndTelemetry', function () {
253254
});
254255

255256
it('automatically sets up device ID for telemetry', async function () {
257+
const abortController = new AbortController();
256258
const loggingAndTelemetry = setupLoggingAndTelemetry({
257259
...testLoggingArguments,
258260
bus,
@@ -263,7 +265,10 @@ describe('MongoshLoggingAndTelemetry', function () {
263265

264266
bus.emit('mongosh:new-user', { userId, anonymousId: userId });
265267

266-
const deviceId = await getDeviceId();
268+
const deviceId = await getDeviceId({
269+
getMachineId: () => getMachineId({ raw: true }),
270+
abortSignal: abortController.signal,
271+
});
267272

268273
await (loggingAndTelemetry as LoggingAndTelemetry).setupTelemetryPromise;
269274

@@ -283,6 +288,50 @@ describe('MongoshLoggingAndTelemetry', function () {
283288
]);
284289
});
285290

291+
it('resolves device ID setup when flushed', async function () {
292+
const loggingAndTelemetry = setupLoggingAndTelemetry({
293+
...testLoggingArguments,
294+
bus,
295+
deviceId: undefined,
296+
});
297+
sinon
298+
// eslint-disable-next-line @typescript-eslint/no-var-requires
299+
.stub(require('native-machine-id'), 'getMachineId')
300+
.resolves(
301+
new Promise((resolve) => setTimeout(resolve, 10_000).unref())
302+
);
303+
304+
loggingAndTelemetry.attachLogger(logger);
305+
306+
// Start the device ID setup
307+
const setupPromise = (loggingAndTelemetry as LoggingAndTelemetry)
308+
.setupTelemetryPromise;
309+
310+
// Flush before it completes
311+
loggingAndTelemetry.flush();
312+
313+
// Emit an event that would trigger analytics
314+
bus.emit('mongosh:new-user', { userId, anonymousId: userId });
315+
316+
await setupPromise;
317+
318+
// Should still identify but with unknown device ID
319+
expect(analyticsOutput).deep.equal([
320+
[
321+
'identify',
322+
{
323+
anonymousId: userId,
324+
traits: {
325+
device_id: 'unknown',
326+
platform: process.platform,
327+
arch: process.arch,
328+
session_id: logId,
329+
},
330+
},
331+
],
332+
]);
333+
});
334+
286335
it('only delays analytic outputs, not logging', async function () {
287336
// eslint-disable-next-line @typescript-eslint/no-empty-function
288337
let resolveTelemetry: (value: unknown) => void = () => {};
@@ -1184,13 +1233,4 @@ describe('MongoshLoggingAndTelemetry', function () {
11841233
],
11851234
]);
11861235
});
1187-
1188-
describe('getDeviceId()', function () {
1189-
it('is consistent on the same machine', async function () {
1190-
const idA = await getDeviceId();
1191-
const idB = await getDeviceId();
1192-
1193-
expect(idA).equals(idB);
1194-
});
1195-
});
11961236
});

packages/logging/src/logging-and-telemetry.ts

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -53,40 +53,7 @@ import type {
5353
MongoshLoggingAndTelemetryArguments,
5454
MongoshTrackingProperties,
5555
} from './types';
56-
import { createHmac } from 'crypto';
57-
58-
/**
59-
* @returns A hashed, unique identifier for the running device or `"unknown"` if not known.
60-
*/
61-
export async function getDeviceId({
62-
onError,
63-
}: {
64-
onError?: (error: Error) => void;
65-
} = {}): Promise<string | 'unknown'> {
66-
try {
67-
// Create a hashed format from the all uppercase version of the machine ID
68-
// to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.
69-
const originalId: string =
70-
// eslint-disable-next-line @typescript-eslint/no-var-requires
71-
await require('native-machine-id').getMachineId({
72-
raw: true,
73-
});
74-
75-
if (!originalId) {
76-
return 'unknown';
77-
}
78-
const hmac = createHmac('sha256', originalId);
79-
80-
/** This matches the message used to create the hashes in Atlas CLI */
81-
const DEVICE_ID_HASH_MESSAGE = 'atlascli';
82-
83-
hmac.update(DEVICE_ID_HASH_MESSAGE);
84-
return hmac.digest('hex');
85-
} catch (error) {
86-
onError?.(error as Error);
87-
return 'unknown';
88-
}
89-
}
56+
import { getDeviceId } from '@mongodb-js/device-id';
9057

9158
export function setupLoggingAndTelemetry(
9259
props: MongoshLoggingAndTelemetryArguments
@@ -125,11 +92,11 @@ export class LoggingAndTelemetry implements MongoshLoggingAndTelemetry {
12592
private isBufferingTelemetryEvents = false;
12693

12794
private deviceId: string | undefined;
128-
/** @internal */
95+
96+
/** @internal Used for awaiting the telemetry setup in tests. */
12997
public setupTelemetryPromise: Promise<void> = Promise.resolve();
13098

131-
// eslint-disable-next-line @typescript-eslint/no-empty-function
132-
private resolveDeviceId: (value: string) => void = () => {};
99+
private readonly telemetrySetupAbort: AbortController = new AbortController();
133100

134101
constructor({
135102
bus,
@@ -160,26 +127,34 @@ export class LoggingAndTelemetry implements MongoshLoggingAndTelemetry {
160127
}
161128

162129
public flush(): void {
163-
// Run any telemetry events even if device ID hasn't been resolved yet
164-
this.runAndClearPendingTelemetryEvents();
165-
166130
// Run any other pending events with the set or dummy log for telemetry purposes.
167131
this.runAndClearPendingBusEvents();
168132

169-
this.resolveDeviceId('unknown');
133+
// Abort setup, which will cause the device ID to be set to 'unknown'
134+
// and run any remaining telemetry events
135+
this.telemetrySetupAbort.abort();
170136
}
171137

172138
private async setupTelemetry(): Promise<void> {
173139
if (!this.deviceId) {
174-
this.deviceId = await Promise.race([
175-
getDeviceId({
176-
onError: (error) =>
177-
this.bus.emit('mongosh:error', error, 'telemetry'),
178-
}),
179-
new Promise<string>((resolve) => {
180-
this.resolveDeviceId = resolve;
181-
}),
182-
]);
140+
try {
141+
// eslint-disable-next-line @typescript-eslint/no-var-requires
142+
const getMachineId = require('native-machine-id').getMachineId;
143+
this.deviceId = await getDeviceId({
144+
getMachineId: () => getMachineId({ raw: true }),
145+
onError: (reason, error) => {
146+
if (reason === 'abort') {
147+
return;
148+
}
149+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
150+
this.bus.emit('mongosh:error', error, 'telemetry');
151+
},
152+
abortSignal: this.telemetrySetupAbort.signal,
153+
});
154+
} catch (error) {
155+
this.deviceId = 'unknown';
156+
this.bus.emit('mongosh:error', error as Error, 'telemetry');
157+
}
183158
}
184159

185160
this.runAndClearPendingTelemetryEvents();

0 commit comments

Comments
 (0)