Skip to content

feat: update connectionString appName param - [MCP-68] #406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open

Conversation

blva
Copy link
Collaborator

@blva blva commented Jul 28, 2025

Proposed changes

  • Refactored telemetry to use centralized device ID utility
  • Extended appName format to appName--deviceId--clientName
  • Created src/helpers/deviceId.ts utility for device ID retrieval
  • Updated setAppNameParamIfMissing function to support extended format
  • Modified Session and Atlas connect tools to use new format
  • Added unit tests and integration tests
  • Decoupled connectionString validation from connection

Checklist

@blva blva marked this pull request as ready for review July 31, 2025 13:13
@Copilot Copilot AI review requested due to automatic review settings July 31, 2025 13:13
@blva blva requested a review from a team as a code owner July 31, 2025 13:13
Copilot

This comment was marked as outdated.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@blva blva requested a review from Copilot July 31, 2025 14:34
Copilot

This comment was marked as outdated.

@blva blva requested a review from fmenezes July 31, 2025 15:34

private constructor(
private readonly session: Session,
private readonly userConfig: UserConfig,
private readonly commonProperties: CommonProperties,
{ eventCache, getRawMachineId }: { eventCache: EventCache; getRawMachineId: () => Promise<string> }
{ eventCache }: { eventCache: EventCache }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think it'd still be good to pass getDeviceIdForConnection for explicit DI. It also helps us avoid mocking the file which is a bit of JavaScript magic

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done! thanks

Copy link
Collaborator

@gagik gagik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's important in my opinion to avoid double device ID resolution

}

const deviceId = components.deviceId || (await getDeviceIdForConnection());
Copy link
Collaborator

@gagik gagik Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't end up awaiting device ID a second time, can we organize it so this device ID gets passed from one point of entry and we never fallback to this function? deviceId doesn't get passed in connectToMongoDB right now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored this quite a bit into a singleton so that we avoid double resolution, let me know what you think and if I'm missing any gaps!

if (!searchParams.has("appName") && defaultAppName !== undefined) {
searchParams.set("appName", defaultAppName);
// Only set appName if it's not already present
if (searchParams.has("appName")) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Compass / mongosh we only pass the device ID if it's an Atlas host to minimize visibility of this, can we also do this here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused, the point of deviceID is to get the separate users that don't authenticate, so we can identify them as unique, in that sense I think we should always keep it. This was also requested so I'm inclined to always keep it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

essentially the idea there was to minimize visibility of this app name setting when they connect to i.e. localhost so for users they don't see identifiers appended to the app name they wouldn't expected but it's not a big deal to keep it as is yeah

@mongodb-js mongodb-js deleted a comment from coveralls Aug 18, 2025
@blva
Copy link
Collaborator Author

blva commented Aug 18, 2025

should be ready for review

@blva blva requested a review from gagik August 18, 2025 16:59
@blva blva requested a review from nirinchev August 19, 2025 11:46
@@ -30,8 +31,8 @@ export const LogId = {
telemetryEmitStart: mongoLogId(1_002_003),
telemetryEmitSuccess: mongoLogId(1_002_004),
telemetryMetadataError: mongoLogId(1_002_005),
telemetryDeviceIdFailure: mongoLogId(1_002_006),
telemetryDeviceIdTimeout: mongoLogId(1_002_007),
deviceIdResolutionError: mongoLogId(1_002_006),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nit: we can still keep the telemetry prefix since this is still what it's related to (and we have this prefix group pattern right now)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but now resolution could fail upon connection tool since this will now be used in two places, no?

Comment on lines +25 to +40
this.startDeviceIdCalculation();
}

/**
* Initializes the DeviceIdService singleton with a logger.
* A separated init method is used to use a single instance of the logger.
* @param logger - The logger instance to use
* @returns The DeviceIdService instance
*/
public static init(logger: LoggerBase, timeout?: number): DeviceIdService {
if (DeviceIdService.instance) {
return DeviceIdService.instance;
}
DeviceIdService.instance = new DeviceIdService(logger, timeout ?? DEVICE_ID_TIMEOUT);
return DeviceIdService.instance;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally prefer to avoid side-effects in constructors i.e. this.startDeviceIdCalculation()
Since we already have an init pattern, we could make this constructor private and have one create(...) will also call the startDeviceIdCalculation(); after creating it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Telemetry service is example of that

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool! I think there's no harm in clarifying this one, but bundling it with your comment below are yo suggesting to do something like telemetry but still maintain a singleton? or just use the same approach and inject the logger object on getDeviceId calls?

Comment on lines +42 to +59
/**
* Checks if the DeviceIdService is initialized.
* @returns True if the DeviceIdService is initialized, false otherwise
*/
public static isInitialized(): boolean {
return DeviceIdService.instance !== undefined;
}

/**
* Gets the singleton instance of DeviceIdService.
* @returns The DeviceIdService instance
*/
public static getInstance(): DeviceIdService {
if (!DeviceIdService.instance) {
throw new Error("DeviceIdService not initialized");
}
return DeviceIdService.instance;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this singleton instance pattern discourages explicit dependency injection which makes it too easy to end up accessing this service prematurely, thus needing these checks to make sure it's initialized. I get it from perspective of only doing the resolution once so we could still keep the static instance as far as create goes and maybe throw an error if someone tries to re-create the service.

But other than that, the getInstance is only used in tests right now and I think for that it'd have been preferable to create the DeviceIdService inside the test and pass it to whatever is being tested instead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants