Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 55 additions & 11 deletions packages/device-id/src/get-device-id.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ describe('getDeviceId', function () {

const deviceId = await getDeviceId({
getMachineId,
isNodeMachineId: false,
}).value;

expect(deviceId).to.be.a('string');
Expand All @@ -22,12 +21,10 @@ describe('getDeviceId', function () {

const resultA = await getDeviceId({
getMachineId,
isNodeMachineId: true,
}).value;

const resultB = await getDeviceId({
getMachineId: () => Promise.resolve(mockMachineId.toUpperCase()),
isNodeMachineId: true,
}).value;

expect(resultA).to.equal(resultB);
Expand All @@ -39,7 +36,6 @@ describe('getDeviceId', function () {

const deviceId = await getDeviceId({
getMachineId,
isNodeMachineId: false,
onError: (error) => {
capturedError = error;
},
Expand All @@ -56,7 +52,6 @@ describe('getDeviceId', function () {

const result = await getDeviceId({
getMachineId,
isNodeMachineId: false,
onError: (err) => {
capturedError = err;
},
Expand All @@ -72,18 +67,16 @@ describe('getDeviceId', function () {

const resultA = await getDeviceId({
getMachineId,
isNodeMachineId: false,
}).value;

const resultB = await getDeviceId({
getMachineId,
isNodeMachineId: false,
}).value;

expect(resultA).to.equal(resultB);
});

it('handles timeout when getting machine id', async function () {
it('resolves timeout with "unknown" by default', async function () {
let timeoutId: NodeJS.Timeout;
const getMachineId = () =>
new Promise<string>((resolve) => {
Expand All @@ -93,7 +86,6 @@ describe('getDeviceId', function () {
let errorCalled = false;
const result = await getDeviceId({
getMachineId,
isNodeMachineId: false,
onError: () => {
errorCalled = true;
},
Expand All @@ -106,6 +98,60 @@ describe('getDeviceId', function () {
expect(errorCalled).to.equal(false);
});

it('resolves with result of onTimeout when successful', async function () {
let timeoutId: NodeJS.Timeout;
const getMachineId = () =>
new Promise<string>((resolve) => {
timeoutId = setTimeout(() => resolve('delayed-id'), 10_000);
});

let errorCalled = false;
const result = await getDeviceId({
getMachineId,
onError: () => {
errorCalled = true;
},
timeout: 1,
onTimeout: () => {
return 'abc-123';
},
}).value;

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
clearTimeout(timeoutId!);
expect(result).to.equal('abc-123');
expect(errorCalled).to.equal(false);
});

it('rejects with an error if onTimeout throws', async function () {
let timeoutId: NodeJS.Timeout;
const getMachineId = () =>
new Promise<string>((resolve) => {
timeoutId = setTimeout(() => resolve('delayed-id'), 10_000);
});

let errorCalled = false;
try {
await getDeviceId({
getMachineId,
onError: () => {
errorCalled = true;
},
timeout: 1,
onTimeout: () => {
throw new Error('Operation timed out');
},
}).value;
expect.fail('Expected promise to be rejected');
} catch (error) {
expect((error as Error).message).to.equal('Operation timed out');
expect(errorCalled).to.equal(false);
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
clearTimeout(timeoutId!);
});

it('handles external promise resolution', async function () {
let timeoutId: NodeJS.Timeout;
const getMachineId = () =>
Expand All @@ -115,7 +161,6 @@ describe('getDeviceId', function () {

const { resolve, value } = getDeviceId({
getMachineId,
isNodeMachineId: false,
});

resolve('external-id');
Expand All @@ -140,7 +185,6 @@ describe('getDeviceId', function () {

const { reject, value } = getDeviceId({
getMachineId,
isNodeMachineId: false,
});

reject(error);
Expand Down
27 changes: 7 additions & 20 deletions packages/device-id/src/get-device-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { createHmac } from 'crypto';

export function getDeviceId({
getMachineId,
isNodeMachineId,
onError,
timeout = 3000,
onTimeout,
Expand All @@ -21,15 +20,14 @@ export function getDeviceId({
const value = Promise.race([
resolveMachineId({
getMachineId,
isNodeMachineId,
onError,
}),
new Promise<string>((resolve, reject) => {
timeoutId = setTimeout(() => {
if (onTimeout) {
onTimeout(resolve, reject);
} else {
resolve('unknown');
try {
resolve(onTimeout?.() ?? 'unknown');
} catch (error) {
reject(error);
}
}, timeout).unref?.();

Expand All @@ -48,28 +46,20 @@ export function getDeviceId({
export type GetDeviceIdOptions = {
/** A function that returns a raw machine ID. */
getMachineId: () => Promise<string | undefined>;
/** When using node-machine-id, the ID is made uppercase to be consistent with other libraries. */
isNodeMachineId: boolean;
/** Runs when an error occurs while getting the machine ID. */
onError?: (error: Error) => void;
/** Timeout in milliseconds. Defaults to 3000ms. Set to `undefined` to disable. */
timeout?: number | undefined;
/** Runs when the timeout is reached. By default, resolves to "unknown". */
onTimeout?: (
resolve: (value: string) => void,
reject: (err: Error) => void,
) => void;
onTimeout?: () => PromiseLike<string> | string;
};

async function resolveMachineId({
getMachineId,
isNodeMachineId,
onError,
}: GetDeviceIdOptions): Promise<string> {
try {
const originalId = isNodeMachineId
? (await getMachineId())?.toUpperCase()
: await getMachineId();
const originalId = (await getMachineId())?.toUpperCase();

if (!originalId) {
onError?.(new Error('Failed to resolve machine ID'));
Expand All @@ -78,10 +68,7 @@ async function resolveMachineId({

// Create a hashed format from the machine ID
// to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.
const hmac = createHmac(
'sha256',
isNodeMachineId ? originalId : originalId,
);
const hmac = createHmac('sha256', originalId);

/** This matches the message used to create the hashes in Atlas CLI */
const DEVICE_ID_HASH_MESSAGE = 'atlascli';
Expand Down
Loading