Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions resources/buildConfigDefinitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const nestedOptionTypes = [
/** The prefix of environment variables for nested options. */
const nestedOptionEnvPrefix = {
AccountLockoutOptions: 'PARSE_SERVER_ACCOUNT_LOCKOUT_',
ClientMetadata: 'PARSE_SERVER_DATABASE_OPTIONS_CLIENT_METADATA_',
CustomPagesOptions: 'PARSE_SERVER_CUSTOM_PAGES_',
DatabaseOptions: 'PARSE_SERVER_DATABASE_',
FileUploadOptions: 'PARSE_SERVER_FILE_UPLOAD_',
Expand Down
24 changes: 24 additions & 0 deletions spec/GridFSBucketStorageAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,4 +475,28 @@ describe_only_db('mongo')('GridFSBucket', () => {
expect(e.message).toEqual('Client must be connected before running operations');
}
});

describe('MongoDB Client Metadata', () => {
it('should not pass metadata to MongoClient by default', async () => {
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
await gfsAdapter._connect();
const driverInfo = gfsAdapter._client.s.options.driverInfo;
// Either driverInfo should be undefined, or it should not contain our custom metadata
if (driverInfo) {
expect(driverInfo.name).toBeUndefined();
}
await gfsAdapter.handleShutdown();
});

it('should pass custom metadata to MongoClient when configured', async () => {
const customMetadata = { name: 'MyParseServer', version: '1.0.0' };
const gfsAdapter = new GridFSBucketAdapter(databaseURI, {
clientMetadata: customMetadata
});
await gfsAdapter._connect();
expect(gfsAdapter._client.s.options.driverInfo.name).toBe(customMetadata.name);
expect(gfsAdapter._client.s.options.driverInfo.version).toBe(customMetadata.version);
await gfsAdapter.handleShutdown();
});
});
});
25 changes: 25 additions & 0 deletions spec/MongoStorageAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1063,4 +1063,29 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
await adapter.handleShutdown();
});
});

describe('MongoDB Client Metadata', () => {
it('should not pass metadata to MongoClient by default', async () => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
await adapter.connect();
const driverInfo = adapter.client.s.options.driverInfo;
// Either driverInfo should be undefined, or it should not contain our custom metadata
if (driverInfo) {
expect(driverInfo.name).toBeUndefined();
}
await adapter.handleShutdown();
});

it('should pass custom metadata to MongoClient when configured', async () => {
const customMetadata = { name: 'MyParseServer', version: '1.0.0' };
const adapter = new MongoStorageAdapter({
uri: databaseURI,
mongoOptions: { clientMetadata: customMetadata }
});
await adapter.connect();
expect(adapter.client.s.options.driverInfo.name).toBe(customMetadata.name);
expect(adapter.client.s.options.driverInfo.version).toBe(customMetadata.version);
await adapter.handleShutdown();
});
});
});
13 changes: 12 additions & 1 deletion src/Adapters/Files/GridFSBucketAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
_connectionPromise: Promise<Db>;
_mongoOptions: Object;
_algorithm: string;
_clientMetadata: ?{ name: string, version: string };

constructor(
mongoDatabaseURI = defaults.DefaultMongoURI,
Expand All @@ -36,6 +37,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
: null;
const defaultMongoOptions = {};
const _mongoOptions = Object.assign(defaultMongoOptions, mongoOptions);
this._clientMetadata = mongoOptions.clientMetadata;
// Remove Parse Server-specific options that should not be passed to MongoDB client
for (const key of ParseServerDatabaseOptions) {
delete _mongoOptions[key];
Expand All @@ -45,7 +47,16 @@ export class GridFSBucketAdapter extends FilesAdapter {

_connect() {
if (!this._connectionPromise) {
this._connectionPromise = MongoClient.connect(this._databaseURI, this._mongoOptions).then(
// Only use driverInfo if clientMetadata option is set
const options = { ...this._mongoOptions };
if (this._clientMetadata) {
options.driverInfo = {
name: this._clientMetadata.name,
version: this._clientMetadata.version
};
}

this._connectionPromise = MongoClient.connect(this._databaseURI, options).then(
client => {
this._client = client;
return client.db(client.s.options.dbName);
Expand Down
14 changes: 13 additions & 1 deletion src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export class MongoStorageAdapter implements StorageAdapter {
_onchange: any;
_stream: any;
_logClientEvents: ?Array<any>;
_clientMetadata: ?{ name: string, version: string };
// Public
connectionPromise: ?Promise<any>;
database: any;
Expand All @@ -156,6 +157,7 @@ export class MongoStorageAdapter implements StorageAdapter {
this.schemaCacheTtl = mongoOptions.schemaCacheTtl;
this.disableIndexFieldValidation = !!mongoOptions.disableIndexFieldValidation;
this._logClientEvents = mongoOptions.logClientEvents;
this._clientMetadata = mongoOptions.clientMetadata;

// Create a copy of mongoOptions and remove Parse Server-specific options that should not
// be passed to MongoDB client. Note: We only delete from this._mongoOptions, not from the
Expand All @@ -179,7 +181,17 @@ export class MongoStorageAdapter implements StorageAdapter {
// parsing and re-formatting causes the auth value (if there) to get URI
// encoded
const encodedUri = formatUrl(parseUrl(this._uri));
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions)

// Only use driverInfo if clientMetadata option is set
const options = { ...this._mongoOptions };
if (this._clientMetadata) {
options.driverInfo = {
name: this._clientMetadata.name,
version: this._clientMetadata.version
};
}

this.connectionPromise = MongoClient.connect(encodedUri, options)
.then(client => {
// Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client
// Fortunately, we can get back the options and use them to select the proper DB.
Expand Down
19 changes: 19 additions & 0 deletions src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ module.exports.ParseServerOptions = {
env: 'PARSE_SERVER_CLIENT_KEY',
help: 'Key for iOS, MacOS, tvOS clients',
},
clientMetadata: {
env: 'PARSE_SERVER_CLIENT_METADATA',
help:
"Custom metadata to append to database client connections for identifying Parse Server instances in database logs. If set, this metadata will be visible in database logs during connection handshakes. This can help with debugging and monitoring in deployments with multiple database clients. Set `name` to identify your application (e.g., 'MyApp') and `version` to your application's version. Leave undefined (default) to disable this feature and avoid the additional data transfer overhead.",
action: parsers.objectParser,
type: 'ClientMetadata',
},
cloud: {
env: 'PARSE_SERVER_CLOUD',
help: 'Full path to your cloud code main.js',
Expand Down Expand Up @@ -1461,6 +1468,18 @@ module.exports.DatabaseOptions = {
action: parsers.numberParser('zlibCompressionLevel'),
},
};
module.exports.ClientMetadata = {
name: {
env: 'PARSE_SERVER_DATABASE_OPTIONS_CLIENT_METADATA_NAME',
help: "The name to identify your application in database logs (e.g., 'MyApp').",
required: true,
},
version: {
env: 'PARSE_SERVER_DATABASE_OPTIONS_CLIENT_METADATA_VERSION',
help: "The version of your application (e.g., '1.0.0').",
required: true,
},
};
module.exports.AuthAdapter = {
enabled: {
help: 'Is `true` if the auth adapter is enabled, `false` otherwise.',
Expand Down
7 changes: 7 additions & 0 deletions src/Options/docs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export interface ParseServerOptions {
databaseOptions: ?DatabaseOptions;
/* Adapter module for the database; any options that are not explicitly described here are passed directly to the database client. */
databaseAdapter: ?Adapter<StorageAdapter>;
/* Custom metadata to append to database client connections for identifying Parse Server instances in database logs. If set, this metadata will be visible in database logs during connection handshakes. This can help with debugging and monitoring in deployments with multiple database clients. Set `name` to identify your application (e.g., 'MyApp') and `version` to your application's version. Leave undefined (default) to disable this feature and avoid the additional data transfer overhead. */
clientMetadata: ?ClientMetadata;
/* Optional. If set to `true`, the collation rule of case comparison for queries and indexes is enabled. Enable this option to run Parse Server with MongoDB Atlas Serverless or AWS Amazon DocumentDB. If `false`, the collation rule of case comparison is disabled. Default is `false`.
:DEFAULT: false */
enableCollationCaseComparison: ?boolean;
Expand Down Expand Up @@ -757,6 +759,13 @@ export interface DatabaseOptions {
logClientEvents: ?(LogClientEvent[]);
}

export interface ClientMetadata {
/* The name to identify your application in database logs (e.g., 'MyApp'). */
name: string;
/* The version of your application (e.g., '1.0.0'). */
version: string;
}

export interface AuthAdapter {
/* Is `true` if the auth adapter is enabled, `false` otherwise.
:DEFAULT: false
Expand Down
1 change: 1 addition & 0 deletions src/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const DefaultMongoURI = DefinitionDefaults.databaseURI;
// before passing to MongoDB client
export const ParseServerDatabaseOptions = [
'allowPublicExplain',
'clientMetadata',
'createIndexRoleName',
'createIndexUserEmail',
'createIndexUserEmailCaseInsensitive',
Expand Down
Loading