Skip to content

Commit 4492cf3

Browse files
authored
feat: add async device ID retrieval COMPASS-8443 (#529)
We'd need to retrieve the device ID asynchronously to avoid blocking the main thread for this so this adds asynchronous worker code.
1 parent 0cba647 commit 4492cf3

File tree

9 files changed

+317
-104
lines changed

9 files changed

+317
-104
lines changed

packages/native-machine-id/README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,32 @@ npx native-machine-id --raw
2020
### As a module
2121

2222
```javascript
23-
import { getMachineID } from 'native-machine-id';
23+
import { getMachineId } from 'native-machine-id';
24+
25+
// Get the machine ID, hashed with SHA-256
26+
const hashedId = getMachineId();
27+
console.log('Hashed Machine ID:', hashedId);
2428

25-
// Get the machine ID
26-
const hashedId = getMachineID();
27-
console.log('SHA-256 Hashed Machine ID:', hashedId);
28-
const rawId = getMachineID({ raw: true });
29+
// Get the raw machine ID (should not be exposed in untrusted environments)
30+
const rawId = getMachineId({ raw: true });
2931
console.log('Original Machine ID:', rawId);
32+
33+
// Or synchronously
34+
import { getMachineIdSync } from 'native-machine-id';
35+
const id = getMachineIdSync();
3036
```
3137

3238
## Supported Platforms
3339

34-
- **macOS**: Uses the `IOPlatformUUID` from the `IOKit` framework (Supported on macOS 12.0 and later).
40+
- **macOS**: Uses the `IOPlatformUUID` from the `IOKit` framework.
3541
- **Linux**: Uses the `/var/lib/dbus/machine-id` file to retrieve the machine ID. If this file does not exist, it falls back to `/etc/machine-id`.
3642
- **Windows**: Uses the `MachineGuid` from the `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography` registry.
3743

3844
## Comparison with `node-machine-id`
3945

4046
This module provides similar functionality to [node-machine-id](https://www.npmjs.com/package/node-machine-id) while **using native access to system APIs without the need for child processes**, making it much faster and reliable.
4147

42-
Here's a table of performance comparisons between the two libraries, based on the average runtime from 1000 iterations of the `getMachineId` and `machineIdSync` functions, from `scripts/benchmark.ts`:
48+
Here's a table of performance comparisons between the two libraries, based on the average runtime from 1000 iterations of the `getMachineIdSync` and `machineIdSync` functions, from `scripts/benchmark.ts`:
4349

4450
| Test | node-machine-id | native-machine-id | Improvement |
4551
| ----------- | --------------- | ----------------- | ----------- |
@@ -61,10 +67,10 @@ If you were previously using `node-machine-id`, you can use the following mappin
6167

6268
```ts
6369
import { createHash } from 'crypto';
64-
import { getMachineId } from 'native-machine-id';
70+
import { getMachineIdSync } from 'native-machine-id';
6571

6672
function machineIdSync(original: boolean): string | undefined {
67-
const rawMachineId = getMachineId({ raw: true }).toLowerCase();
73+
const rawMachineId = getMachineIdSync({ raw: true }).toLowerCase();
6874

6975
return original
7076
? rawMachineId

packages/native-machine-id/binding.cc

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#ifdef __APPLE__
55
#include <CoreFoundation/CoreFoundation.h>
66
#include <IOKit/IOKitLib.h>
7+
// kIOMainPortDefault and kIOMasterPortDefault are both set to 0
8+
// and we define them here to avoid conflicts across OS versions
9+
#define IO_PORT 0
710
#elif defined(__linux__)
811
#include <fstream>
912
#include <algorithm>
@@ -21,7 +24,7 @@ namespace
2124
std::string getMachineId() noexcept
2225
{
2326
std::string uuid;
24-
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMainPortDefault, "IOService:/");
27+
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(IO_PORT, "IOService:/");
2528

2629
if (ioRegistryRoot == MACH_PORT_NULL)
2730
{
@@ -98,7 +101,7 @@ namespace
98101
}
99102

100103
// Get Linux machine ID by reading from system files
101-
std::string getMachineId()
104+
std::string getMachineId() noexcept
102105
{
103106
std::string uuid = readFile(DBUS_PATH);
104107

@@ -112,7 +115,7 @@ namespace
112115
}
113116
#elif defined(_WIN32)
114117
// Get Windows machine ID from registry
115-
std::string getMachineId()
118+
std::string getMachineId() noexcept
116119
{
117120
std::string uuid;
118121
HKEY hKey;
@@ -156,8 +159,8 @@ namespace
156159
}
157160
#endif
158161

159-
// Function to get the machine ID
160-
Value GetMachineId(const CallbackInfo &args)
162+
// Function to get the machine ID (synchronous version)
163+
Value GetMachineIdSync(const CallbackInfo &args)
161164
{
162165
Env env = args.Env();
163166

@@ -173,12 +176,76 @@ namespace
173176
return env.Undefined();
174177
}
175178

179+
// Async worker class for getting machine ID
180+
class GetMachineIdWorker : public AsyncWorker
181+
{
182+
private:
183+
std::string id;
184+
185+
public:
186+
GetMachineIdWorker(Function &callback)
187+
: AsyncWorker(callback) {}
188+
189+
// This runs on a worker thread
190+
void Execute() override
191+
{
192+
#if defined(__APPLE__) || defined(__linux__) || defined(_WIN32)
193+
id = getMachineId();
194+
#endif
195+
}
196+
197+
// This runs on the main thread after Execute completes
198+
void OnOK() override
199+
{
200+
Napi::Env env = Env();
201+
Napi::HandleScope scope(env);
202+
203+
if (!id.empty())
204+
{
205+
Callback().Call({env.Null(), String::New(env, id)});
206+
}
207+
else
208+
{
209+
Callback().Call({env.Null(), env.Undefined()});
210+
}
211+
}
212+
213+
void OnError(const Error &e) override
214+
{
215+
Napi::Env env = Env();
216+
Napi::HandleScope scope(env);
217+
Callback().Call({e.Value(), env.Undefined()});
218+
}
219+
};
220+
221+
// Function to get the machine ID asynchronously
222+
Value GetMachineIdAsync(const CallbackInfo &args)
223+
{
224+
Env env = args.Env();
225+
226+
// Check if a callback was provided
227+
if (args.Length() < 1 || !args[0].IsFunction())
228+
{
229+
TypeError::New(env, "Callback function expected").ThrowAsJavaScriptException();
230+
return env.Undefined();
231+
}
232+
233+
// Get the callback function
234+
Function callback = args[0].As<Function>();
235+
236+
// Create and queue the async worker
237+
GetMachineIdWorker *worker = new GetMachineIdWorker(callback);
238+
worker->Queue();
239+
240+
return env.Undefined();
241+
}
176242
}
177243

178244
static Object InitModule(Env env, Object exports)
179245
{
180-
exports["getMachineId"] = Function::New(env, GetMachineId);
246+
exports["getMachineIdSync"] = Function::New(env, GetMachineIdSync);
247+
exports["getMachineIdAsync"] = Function::New(env, GetMachineIdAsync);
181248
return exports;
182249
}
183250

184-
NODE_API_MODULE(machine_id, InitModule)
251+
NODE_API_MODULE(native_machine_id, InitModule)

packages/native-machine-id/binding.gyp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
'targets': [{
3-
'target_name': 'machine_id',
3+
'target_name': 'native_machine_id',
44
'sources': [ 'binding.cc' ],
55
'include_dirs': ["<!(node -p \"require('node-addon-api').include_dir\")"],
66
'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],

packages/native-machine-id/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
22
"name": "native-machine-id",
3-
"version": "0.0.3",
3+
"version": "0.0.8",
44
"description": "Native retrieval of a unique desktop machine ID without admin privileges or child processes. Faster and more reliable alternative to node-machine-id.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"scripts": {
88
"compile": "tsc -p tsconfig.json && node-gyp rebuild && gen-esm-wrapper . ./dist/.esm-wrapper.mjs",
99
"bootstrap": "npm run compile",
1010
"pretest": "npm run compile",
11+
"install": "node-gyp rebuild",
1112
"test": "mocha",
1213
"test-cov": "nyc -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test",
1314
"test-ci": "npm run test-cov",
@@ -25,7 +26,8 @@
2526
"license": "Apache-2.0",
2627
"exports": {
2728
"require": "./dist/index.js",
28-
"import": "./dist/.esm-wrapper.mjs"
29+
"import": "./dist/.esm-wrapper.mjs",
30+
"types": "./dist/index.d.ts"
2931
},
3032
"homepage": "https://github.com/mongodb-js/devtools-shared",
3133
"repository": {
@@ -34,7 +36,7 @@
3436
},
3537
"bugs": "https://jira.mongodb.org/projects/COMPASS/issues",
3638
"bin": {
37-
"machine-id": "dist/bin/machine-id.js"
39+
"native-machine-id": "dist/bin/machine-id.js"
3840
},
3941
"files": [
4042
"binding.cc",

packages/native-machine-id/scripts/benchmark.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* against the node-machine-id package.
88
*/
99

10-
import { getMachineId } from '../dist/index.js';
10+
import { getMachineIdSync as getMachineId } from '../dist/index.js';
1111
import { machineIdSync } from 'node-machine-id';
1212

1313
// Configuration
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
2-
import { getMachineId } from '..';
2+
import { getMachineIdSync } from '..';
33

4-
const id = getMachineId({ raw: process.argv.includes('--raw') }) || '';
4+
const id = getMachineIdSync({ raw: process.argv.includes('--raw') }) || '';
55

66
// eslint-disable-next-line no-console
77
console.log(id);
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
declare module 'bindings' {
2-
function bindings(filename: 'machine_id'): {
3-
getMachineId: () => string | undefined;
2+
function bindings(filename: 'native_machine_id'): {
3+
getMachineIdSync: () => string | undefined;
4+
getMachineIdAsync: (
5+
callback: (err: Error | null, id: string | undefined) => void,
6+
) => void;
47
};
58
export = bindings;
69
}

0 commit comments

Comments
 (0)