diff --git a/lib/cmap/connection_pool.js b/lib/cmap/connection_pool.js index 55305c5185..829075aca6 100644 --- a/lib/cmap/connection_pool.js +++ b/lib/cmap/connection_pool.js @@ -131,7 +131,8 @@ class ConnectionPool extends EventEmitter { maxIdleTimeMS: typeof options.maxIdleTimeMS === 'number' ? options.maxIdleTimeMS : 0, waitQueueTimeoutMS: typeof options.waitQueueTimeoutMS === 'number' ? options.waitQueueTimeoutMS : 0, - autoEncrypter: options.autoEncrypter + autoEncrypter: options.autoEncrypter, + metadata: options.metadata }); if (options.minSize > options.maxSize) { diff --git a/lib/core/connection/connect.js b/lib/core/connection/connect.js index 15cfad5860..2922c61b45 100644 --- a/lib/core/connection/connect.js +++ b/lib/core/connection/connect.js @@ -3,11 +3,11 @@ const net = require('net'); const tls = require('tls'); const Connection = require('./connection'); const Query = require('./commands').Query; -const createClientInfo = require('../topologies/shared').createClientInfo; const MongoError = require('../error').MongoError; const MongoNetworkError = require('../error').MongoNetworkError; const defaultAuthProviders = require('../auth/defaultAuthProviders').defaultAuthProviders; const WIRE_CONSTANTS = require('../wireprotocol/constants'); +const makeClientMetadata = require('../utils').makeClientMetadata; const MAX_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_WIRE_VERSION; const MAX_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_SERVER_VERSION; const MIN_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_WIRE_VERSION; @@ -105,7 +105,7 @@ function performInitialHandshake(conn, options, _callback) { const handshakeDoc = Object.assign( { ismaster: true, - client: createClientInfo(options), + client: options.metadata || makeClientMetadata(options), compression: compressors }, getSaslSupportedMechs(options) diff --git a/lib/core/sdam/server.js b/lib/core/sdam/server.js index 1935c84dd2..7855c2a09d 100644 --- a/lib/core/sdam/server.js +++ b/lib/core/sdam/server.js @@ -4,7 +4,6 @@ const ConnectionPool = require('../../cmap/connection_pool').ConnectionPool; const MongoError = require('../error').MongoError; const relayEvents = require('../utils').relayEvents; const BSON = require('../connection/utils').retrieveBSON(); -const createClientInfo = require('../topologies/shared').createClientInfo; const Logger = require('../connection/logger'); const ServerDescription = require('./server_description').ServerDescription; const ReadPreference = require('../topologies/read_preference'); @@ -99,8 +98,6 @@ class Server extends EventEmitter { BSON.Symbol, BSON.Timestamp ]), - // client metadata for the initial handshake - clientInfo: createClientInfo(options), // the server state state: STATE_CLOSED, credentials: options.credentials, diff --git a/lib/core/sdam/topology.js b/lib/core/sdam/topology.js index 02a65ac6b1..2fbefee707 100644 --- a/lib/core/sdam/topology.js +++ b/lib/core/sdam/topology.js @@ -16,7 +16,6 @@ const createCompressionInfo = require('../topologies/shared').createCompressionI const isRetryableError = require('../error').isRetryableError; const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError; const ClientSession = require('../sessions').ClientSession; -const createClientInfo = require('../topologies/shared').createClientInfo; const MongoError = require('../error').MongoError; const resolveClusterTime = require('../topologies/shared').resolveClusterTime; const SrvPoller = require('./srv_polling').SrvPoller; @@ -25,6 +24,7 @@ const makeStateMachine = require('../utils').makeStateMachine; const eachAsync = require('../utils').eachAsync; const emitDeprecationWarning = require('../../utils').emitDeprecationWarning; const ServerSessionPool = require('../sessions').ServerSessionPool; +const makeClientMetadata = require('../utils').makeClientMetadata; const common = require('./common'); const drainTimerQueue = common.drainTimerQueue; @@ -119,6 +119,13 @@ class Topology extends EventEmitter { } options = Object.assign({}, common.TOPOLOGY_DEFAULTS, options); + options = Object.freeze( + Object.assign(options, { + metadata: makeClientMetadata(options), + compression: { compressors: createCompressionInfo(options) } + }) + ); + DEPRECATED_OPTIONS.forEach(optionName => { if (options[optionName]) { emitDeprecationWarning( @@ -196,12 +203,6 @@ class Topology extends EventEmitter { connectionTimers: new Set() }; - // amend options for server instance creation - this.s.options.compression = { compressors: createCompressionInfo(options) }; - - // add client info - this.s.clientInfo = createClientInfo(options); - if (options.srvHost) { this.s.srvPoller = options.srvPoller || @@ -705,8 +706,8 @@ class Topology extends EventEmitter { return new CursorClass(topology, ns, cmd, options); } - get clientInfo() { - return this.s.clientInfo; + get clientMetadata() { + return this.s.options.metadata; } isConnected() { diff --git a/lib/core/topologies/mongos.js b/lib/core/topologies/mongos.js index 681b01fd70..29371931af 100644 --- a/lib/core/topologies/mongos.js +++ b/lib/core/topologies/mongos.js @@ -8,16 +8,15 @@ const Logger = require('../connection/logger'); const retrieveBSON = require('../connection/utils').retrieveBSON; const MongoError = require('../error').MongoError; const Server = require('./server'); -const clone = require('./shared').clone; const diff = require('./shared').diff; const cloneOptions = require('./shared').cloneOptions; -const createClientInfo = require('./shared').createClientInfo; const SessionMixins = require('./shared').SessionMixins; const isRetryableWritesSupported = require('./shared').isRetryableWritesSupported; const relayEvents = require('../utils').relayEvents; const isRetryableError = require('../error').isRetryableError; const BSON = retrieveBSON(); const getMMAPError = require('./shared').getMMAPError; +const makeClientMetadata = require('../utils').makeClientMetadata; /** * @fileOverview The **Mongos** class is a class that represents a Mongos Proxy topology and is @@ -116,7 +115,7 @@ var Mongos = function(seedlist, options) { // Internal state this.s = { - options: Object.assign({}, options), + options: Object.assign({ metadata: makeClientMetadata(options) }, options), // BSON instance bson: options.bson || @@ -153,14 +152,9 @@ var Mongos = function(seedlist, options) { // Are we running in debug mode debug: typeof options.debug === 'boolean' ? options.debug : false, // localThresholdMS - localThresholdMS: options.localThresholdMS || 15, - // Client info - clientInfo: createClientInfo(options) + localThresholdMS: options.localThresholdMS || 15 }; - // Set the client info - this.s.options.clientInfo = createClientInfo(options); - // Log info warning if the socketTimeout < haInterval as it will cause // a lot of recycled connections to happen. if ( @@ -265,8 +259,7 @@ Mongos.prototype.connect = function(options) { Object.assign({}, self.s.options, x, options, { reconnect: false, monitoring: false, - parent: self, - clientInfo: clone(self.s.clientInfo) + parent: self }) ); @@ -607,8 +600,7 @@ function reconnectProxies(self, proxies, callback) { port: parseInt(_server.name.split(':')[1], 10), reconnect: false, monitoring: false, - parent: self, - clientInfo: clone(self.s.clientInfo) + parent: self }) ); diff --git a/lib/core/topologies/replset.js b/lib/core/topologies/replset.js index 0f03e9940d..b289d59a34 100644 --- a/lib/core/topologies/replset.js +++ b/lib/core/topologies/replset.js @@ -10,10 +10,8 @@ const Logger = require('../connection/logger'); const MongoError = require('../error').MongoError; const Server = require('./server'); const ReplSetState = require('./replset_state'); -const clone = require('./shared').clone; const Timeout = require('./shared').Timeout; const Interval = require('./shared').Interval; -const createClientInfo = require('./shared').createClientInfo; const SessionMixins = require('./shared').SessionMixins; const isRetryableWritesSupported = require('./shared').isRetryableWritesSupported; const relayEvents = require('../utils').relayEvents; @@ -21,6 +19,7 @@ const isRetryableError = require('../error').isRetryableError; const BSON = retrieveBSON(); const calculateDurationInMs = require('../utils').calculateDurationInMs; const getMMAPError = require('./shared').getMMAPError; +const makeClientMetadata = require('../utils').makeClientMetadata; // // States @@ -140,7 +139,7 @@ var ReplSet = function(seedlist, options) { // Internal state this.s = { - options: Object.assign({}, options), + options: Object.assign({ metadata: makeClientMetadata(options) }, options), // BSON instance bson: options.bson || @@ -187,9 +186,7 @@ var ReplSet = function(seedlist, options) { // Connect function options passed in connectOptions: {}, // Are we running in debug mode - debug: typeof options.debug === 'boolean' ? options.debug : false, - // Client info - clientInfo: createClientInfo(options) + debug: typeof options.debug === 'boolean' ? options.debug : false }; // Add handler for topology change @@ -369,8 +366,7 @@ function connectNewServers(self, servers, callback) { port: parseInt(_server.split(':')[1], 10), reconnect: false, monitoring: false, - parent: self, - clientInfo: clone(self.s.clientInfo) + parent: self }) ); @@ -918,8 +914,7 @@ ReplSet.prototype.connect = function(options) { Object.assign({}, self.s.options, x, options, { reconnect: false, monitoring: false, - parent: self, - clientInfo: clone(self.s.clientInfo) + parent: self }) ); }); diff --git a/lib/core/topologies/server.js b/lib/core/topologies/server.js index c81d5e8e40..6f6de12eaa 100644 --- a/lib/core/topologies/server.js +++ b/lib/core/topologies/server.js @@ -13,13 +13,13 @@ var inherits = require('util').inherits, wireProtocol = require('../wireprotocol'), CoreCursor = require('../cursor').CoreCursor, sdam = require('./shared'), - createClientInfo = require('./shared').createClientInfo, createCompressionInfo = require('./shared').createCompressionInfo, resolveClusterTime = require('./shared').resolveClusterTime, SessionMixins = require('./shared').SessionMixins, relayEvents = require('../utils').relayEvents; const collationNotSupported = require('../utils').collationNotSupported; +const makeClientMetadata = require('../utils').makeClientMetadata; // Used for filtering out fields for loggin var debugFields = [ @@ -120,7 +120,7 @@ var Server = function(options) { // Internal state this.s = { // Options - options: options, + options: Object.assign({ metadata: makeClientMetadata(options) }, options), // Logger logger: Logger('Server', options), // Factory overrides @@ -175,8 +175,6 @@ var Server = function(options) { this.initialConnect = true; // Default type this._type = 'server'; - // Set the client info - this.clientInfo = createClientInfo(options); // Max Stalleness values // last time we updated the ismaster state @@ -212,6 +210,13 @@ Object.defineProperty(Server.prototype, 'logicalSessionTimeoutMinutes', { } }); +Object.defineProperty(Server.prototype, 'clientMetadata', { + enumerable: true, + get: function() { + return this.s.options.metadata; + } +}); + // In single server deployments we track the clusterTime directly on the topology, however // in Mongos and ReplSet deployments we instead need to delegate the clusterTime up to the // tracking objects so we can ensure we are gossiping the maximum time received from the diff --git a/lib/core/topologies/shared.js b/lib/core/topologies/shared.js index d69e88bf40..c0d0f14d69 100644 --- a/lib/core/topologies/shared.js +++ b/lib/core/topologies/shared.js @@ -1,8 +1,5 @@ 'use strict'; - -const os = require('os'); const ReadPreference = require('./read_preference'); -const Buffer = require('safe-buffer').Buffer; const TopologyType = require('../sdam/common').TopologyType; const MongoError = require('../error').MongoError; @@ -18,62 +15,6 @@ function emitSDAMEvent(self, event, description) { } } -// Get package.json variable -const driverVersion = require('../../../package.json').version; -const nodejsVersion = `'Node.js ${process.version}, ${os.endianness}`; -const type = os.type(); -const name = process.platform; -const architecture = process.arch; -const release = os.release(); - -function createClientInfo(options) { - const clientInfo = options.clientInfo - ? clone(options.clientInfo) - : { - driver: { - name: 'nodejs', - version: driverVersion - }, - os: { - type: type, - name: name, - architecture: architecture, - version: release - } - }; - - if (options.useUnifiedTopology) { - clientInfo.platform = `${nodejsVersion} (${options.useUnifiedTopology ? 'unified' : 'legacy'})`; - } - - // Do we have an application specific string - if (options.appname) { - // Cut at 128 bytes - var buffer = Buffer.from(options.appname); - // Return the truncated appname - var appname = buffer.length > 128 ? buffer.slice(0, 128).toString('utf8') : options.appname; - // Add to the clientInfo - clientInfo.application = { name: appname }; - } - - // support optionally provided wrapping driver info - if (options.driverInfo) { - if (options.driverInfo.name) { - clientInfo.driver.name = `${clientInfo.driver.name}|${options.driverInfo.name}`; - } - - if (options.driverInfo.version) { - clientInfo.driver.version = `${clientInfo.driver.version}|${options.driverInfo.version}`; - } - - if (options.driverInfo.platform) { - clientInfo.platform = `${clientInfo.platform}|${options.driverInfo.platform}`; - } - } - - return clientInfo; -} - function createCompressionInfo(options) { if (!options.compression || !options.compression.compressors) { return []; @@ -475,7 +416,6 @@ module.exports.getTopologyType = getTopologyType; module.exports.emitServerDescriptionChanged = emitServerDescriptionChanged; module.exports.emitTopologyDescriptionChanged = emitTopologyDescriptionChanged; module.exports.cloneOptions = cloneOptions; -module.exports.createClientInfo = createClientInfo; module.exports.createCompressionInfo = createCompressionInfo; module.exports.clone = clone; module.exports.diff = diff; diff --git a/lib/core/utils.js b/lib/core/utils.js index 9e71f09f47..d9f487db01 100644 --- a/lib/core/utils.js +++ b/lib/core/utils.js @@ -1,5 +1,5 @@ 'use strict'; - +const os = require('os'); const crypto = require('crypto'); const requireOptional = require('require_optional'); @@ -210,6 +210,51 @@ function makeStateMachine(stateTable) { }; } +function makeClientMetadata(options) { + options = options || {}; + + const metadata = { + driver: { + name: 'nodejs', + version: require('../../package.json').version + }, + os: { + type: os.type(), + name: process.platform, + architecture: process.arch, + version: os.release() + }, + platform: `'Node.js ${process.version}, ${os.endianness} (${ + options.useUnifiedTopology ? 'unified' : 'legacy' + })` + }; + + // support optionally provided wrapping driver info + if (options.driverInfo) { + if (options.driverInfo.name) { + metadata.driver.name = `${metadata.driver.name}|${options.driverInfo.name}`; + } + + if (options.driverInfo.version) { + metadata.version = `${metadata.driver.version}|${options.driverInfo.version}`; + } + + if (options.driverInfo.platform) { + metadata.platform = `${metadata.platform}|${options.driverInfo.platform}`; + } + } + + if (options.appname) { + // MongoDB requires the appname not exceed a byte length of 128 + const buffer = Buffer.from(options.appname); + metadata.application = { + name: buffer.length > 128 ? buffer.slice(0, 128).toString('utf8') : options.appname + }; + } + + return metadata; +} + module.exports = { uuidV4, calculateDurationInMs, @@ -224,5 +269,6 @@ module.exports = { arrayStrictEqual, tagsStrictEqual, errorStrictEqual, - makeStateMachine + makeStateMachine, + makeClientMetadata }; diff --git a/lib/topologies/mongos.js b/lib/topologies/mongos.js index 5250a8a581..10e66d2151 100644 --- a/lib/topologies/mongos.js +++ b/lib/topologies/mongos.js @@ -168,13 +168,6 @@ class Mongos extends TopologyBase { // Translate all the options to the core types clonedOptions = translateOptions(clonedOptions, socketOptions); - // Build default client information - clonedOptions.clientInfo = this.clientInfo; - // Do we have an application specific string - if (options.appname) { - clonedOptions.clientInfo.application = { name: options.appname }; - } - // Internal state this.s = { // Create the Mongos diff --git a/lib/topologies/native_topology.js b/lib/topologies/native_topology.js index ce53f7f3e9..778ddc9fab 100644 --- a/lib/topologies/native_topology.js +++ b/lib/topologies/native_topology.js @@ -35,11 +35,6 @@ class NativeTopology extends Topology { clonedOptions = translateOptions(clonedOptions, socketOptions); super(servers, clonedOptions); - - // Do we have an application specific string - if (options.appname) { - this.s.clientInfo.application = { name: options.appname }; - } } capabilities() { diff --git a/lib/topologies/replset.js b/lib/topologies/replset.js index d78ae13b61..69df26d19e 100644 --- a/lib/topologies/replset.js +++ b/lib/topologies/replset.js @@ -175,13 +175,6 @@ class ReplSet extends TopologyBase { // Translate all the options to the core types clonedOptions = translateOptions(clonedOptions, socketOptions); - // Build default client information - clonedOptions.clientInfo = this.clientInfo; - // Do we have an application specific string - if (options.appname) { - clonedOptions.clientInfo.application = { name: options.appname }; - } - // Create the ReplSet var coreTopology = new CReplSet(seedlist, clonedOptions); diff --git a/lib/topologies/server.js b/lib/topologies/server.js index 2d6c65359d..3079cb9953 100644 --- a/lib/topologies/server.js +++ b/lib/topologies/server.js @@ -168,13 +168,6 @@ class Server extends TopologyBase { // Translate all the options to the core types clonedOptions = translateOptions(clonedOptions, socketOptions); - // Build default client information - clonedOptions.clientInfo = this.clientInfo; - // Do we have an application specific string - if (options.appname) { - clonedOptions.clientInfo.application = { name: options.appname }; - } - // Define the internal properties this.s = { // Create an instance of a server instance from core module diff --git a/lib/topologies/topology_base.js b/lib/topologies/topology_base.js index e74cb9ff60..967b4cd462 100644 --- a/lib/topologies/topology_base.js +++ b/lib/topologies/topology_base.js @@ -3,7 +3,6 @@ const EventEmitter = require('events'), MongoError = require('../core').MongoError, f = require('util').format, - os = require('os'), translateReadPreference = require('../utils').translateReadPreference, ClientSession = require('../core').Sessions.ClientSession; @@ -254,33 +253,9 @@ var ServerCapabilities = function(ismaster) { setup_get_property(this, 'commandsTakeCollation', commandsTakeCollation); }; -// Get package.json variable -const driverVersion = require('../../package.json').version, - nodejsversion = f('Node.js %s, %s', process.version, os.endianness()), - type = os.type(), - name = process.platform, - architecture = process.arch, - release = os.release(); - class TopologyBase extends EventEmitter { constructor() { super(); - - // Build default client information - this.clientInfo = { - driver: { - name: 'nodejs', - version: driverVersion - }, - os: { - type: type, - name: name, - architecture: architecture, - version: release - }, - platform: nodejsversion - }; - this.setMaxListeners(Infinity); } @@ -304,6 +279,10 @@ class TopologyBase extends EventEmitter { return this.s.coreTopology.endSessions(sessions, callback); } + get clientMetadata() { + return this.s.coreTopology.s.options.metadata; + } + // Server capabilities capabilities() { if (this.s.sCapabilities) return this.s.sCapabilities; diff --git a/lib/utils.js b/lib/utils.js index 052bcabdad..dd6cbe8ce8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,5 +1,4 @@ 'use strict'; - const MongoError = require('./core/error').MongoError; const ReadPreference = require('./core/topologies/read_preference'); const WriteConcern = require('./write_concern'); diff --git a/test/functional/core/client_metadata.test.js b/test/functional/core/client_metadata.test.js index 7bdcf01445..62089f36f5 100644 --- a/test/functional/core/client_metadata.test.js +++ b/test/functional/core/client_metadata.test.js @@ -22,7 +22,7 @@ describe('Client metadata tests', function() { } ); - expect(server.clientInfo.application.name).to.equal('My application name'); + expect(server.clientMetadata.application.name).to.equal('My application name'); done(); } }); @@ -53,9 +53,8 @@ describe('Client metadata tests', function() { server.on('connect', function(_server) { _server.s.replicaSetState.allServers().forEach(function(x) { - // console.dir(x.clientInfo) - expect(x.clientInfo.application.name).to.equal('My application name'); - expect(x.clientInfo.platform.split('mongodb-core').length).to.equal(2); + expect(x.clientMetadata.application.name).to.equal('My application name'); + expect(x.clientMetadata.platform.split('mongodb-core').length).to.equal(2); }); _server.destroy(done); @@ -86,9 +85,8 @@ describe('Client metadata tests', function() { // Add event listeners _server.once('connect', function(server) { server.connectedProxies.forEach(function(x) { - // console.dir(x.clientInfo) - expect(x.clientInfo.application.name).to.equal('My application name'); - expect(x.clientInfo.platform.split('mongodb-core').length).to.equal(2); + expect(x.clientMetadata.application.name).to.equal('My application name'); + expect(x.clientMetadata.platform.split('mongodb-core').length).to.equal(2); }); server.destroy(done); diff --git a/test/functional/mongo_client.test.js b/test/functional/mongo_client.test.js index a3878c1e57..c46c81aea9 100644 --- a/test/functional/mongo_client.test.js +++ b/test/functional/mongo_client.test.js @@ -642,7 +642,7 @@ describe('MongoClient', function() { const client = configuration.newClient(url); client.connect(function(err, client) { test.equal(null, err); - test.equal('hello world', client.topology.clientInfo.application.name); + test.equal('hello world', client.topology.clientMetadata.application.name); client.close(done); }); @@ -664,7 +664,7 @@ describe('MongoClient', function() { const client = configuration.newClient(url, { appname: 'hello world' }); client.connect(err => { test.equal(null, err); - test.equal('hello world', client.topology.clientInfo.application.name); + test.equal('hello world', client.topology.clientMetadata.application.name); client.close(done); }); diff --git a/test/unit/client_metadata.test.js b/test/unit/client_metadata.test.js new file mode 100644 index 0000000000..21b5127418 --- /dev/null +++ b/test/unit/client_metadata.test.js @@ -0,0 +1,51 @@ +'use strict'; +const mock = require('mongodb-mock-server'); +const expect = require('chai').expect; + +describe('Client Metadata', function() { + let mockServer; + before(() => mock.createServer().then(server => (mockServer = server))); + after(() => mock.cleanup()); + + it('should report the correct platform in client metadata', function(done) { + const ismasters = []; + mockServer.setMessageHandler(request => { + const doc = request.document; + if (doc.ismaster) { + ismasters.push(doc); + request.reply(mock.DEFAULT_ISMASTER); + } else { + request.reply({ ok: 1 }); + } + }); + + const isUnifiedTopology = this.configuration.usingUnifiedTopology(); + const client = this.configuration.newClient(`mongodb://${mockServer.uri()}/`); + client.connect(err => { + expect(err).to.not.exist; + this.defer(() => client.close()); + + client.db().command({ ping: 1 }, err => { + expect(err).to.not.exist; + + if (isUnifiedTopology) { + expect(ismasters).to.have.length.greaterThan(1); + ismasters.forEach(ismaster => + expect(ismaster) + .nested.property('client.platform') + .to.match(/unified/) + ); + } else { + expect(ismasters).to.have.length(1); + ismasters.forEach(ismaster => + expect(ismaster) + .nested.property('client.platform') + .to.match(/legacy/) + ); + } + + done(); + }); + }); + }); +}); diff --git a/test/unit/core/connect.test.js b/test/unit/core/connect.test.js index a3db4940c6..312553771c 100644 --- a/test/unit/core/connect.test.js +++ b/test/unit/core/connect.test.js @@ -100,35 +100,6 @@ describe('Connect Tests', function() { }); }); - it( - 'should report the correct metadata for unified topology', - { requires: { unifiedTopology: true, topology: ['single'] } }, - function(done) { - let ismaster; - test.server.setMessageHandler(request => { - const doc = request.document; - const $clusterTime = genClusterTime(Date.now()); - if (doc.ismaster) { - ismaster = doc; - request.reply( - Object.assign({}, mock.DEFAULT_ISMASTER, { - $clusterTime, - arbiterOnly: true - }) - ); - } - }); - - const topology = this.configuration.newTopology(test.connectOptions); - topology.connect(test.connectOptions, err => { - expect(err).to.not.exist; - const platform = ismaster.client.platform; - expect(platform).to.match(/unified/); - topology.close(done); - }); - } - ); - it('should allow a cancellaton token', function(done) { const cancellationToken = new EventEmitter(); setTimeout(() => cancellationToken.emit('cancel'), 500);