utils.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. 'use strict';
  2. const os = require('os');
  3. const crypto = require('crypto');
  4. const requireOptional = require('optional-require')(require);
  5. /**
  6. * Generate a UUIDv4
  7. */
  8. const uuidV4 = () => {
  9. const result = crypto.randomBytes(16);
  10. result[6] = (result[6] & 0x0f) | 0x40;
  11. result[8] = (result[8] & 0x3f) | 0x80;
  12. return result;
  13. };
  14. /**
  15. * Relays events for a given listener and emitter
  16. *
  17. * @param {EventEmitter} listener the EventEmitter to listen to the events from
  18. * @param {EventEmitter} emitter the EventEmitter to relay the events to
  19. */
  20. function relayEvents(listener, emitter, events) {
  21. events.forEach(eventName => listener.on(eventName, event => emitter.emit(eventName, event)));
  22. }
  23. function retrieveKerberos() {
  24. let kerberos;
  25. try {
  26. // Ensure you always wrap an optional require in the try block NODE-3199
  27. kerberos = requireOptional('kerberos');
  28. } catch (err) {
  29. if (err.code === 'MODULE_NOT_FOUND') {
  30. throw new Error('The `kerberos` module was not found. Please install it and try again.');
  31. }
  32. throw err;
  33. }
  34. return kerberos;
  35. }
  36. // Throw an error if an attempt to use EJSON is made when it is not installed
  37. const noEJSONError = function() {
  38. throw new Error('The `mongodb-extjson` module was not found. Please install it and try again.');
  39. };
  40. // Facilitate loading EJSON optionally
  41. function retrieveEJSON() {
  42. let EJSON = requireOptional('mongodb-extjson');
  43. if (!EJSON) {
  44. EJSON = {
  45. parse: noEJSONError,
  46. deserialize: noEJSONError,
  47. serialize: noEJSONError,
  48. stringify: noEJSONError,
  49. setBSONModule: noEJSONError,
  50. BSON: noEJSONError
  51. };
  52. }
  53. return EJSON;
  54. }
  55. /**
  56. * A helper function for determining `maxWireVersion` between legacy and new topology
  57. * instances
  58. *
  59. * @private
  60. * @param {(Topology|Server)} topologyOrServer
  61. */
  62. function maxWireVersion(topologyOrServer) {
  63. if (topologyOrServer) {
  64. if (topologyOrServer.ismaster) {
  65. return topologyOrServer.ismaster.maxWireVersion;
  66. }
  67. if (typeof topologyOrServer.lastIsMaster === 'function') {
  68. const lastIsMaster = topologyOrServer.lastIsMaster();
  69. if (lastIsMaster) {
  70. return lastIsMaster.maxWireVersion;
  71. }
  72. }
  73. if (topologyOrServer.description) {
  74. return topologyOrServer.description.maxWireVersion;
  75. }
  76. }
  77. return 0;
  78. }
  79. /*
  80. * Checks that collation is supported by server.
  81. *
  82. * @param {Server} [server] to check against
  83. * @param {object} [cmd] object where collation may be specified
  84. * @param {function} [callback] callback function
  85. * @return true if server does not support collation
  86. */
  87. function collationNotSupported(server, cmd) {
  88. return cmd && cmd.collation && maxWireVersion(server) < 5;
  89. }
  90. /**
  91. * Checks if a given value is a Promise
  92. *
  93. * @param {*} maybePromise
  94. * @return true if the provided value is a Promise
  95. */
  96. function isPromiseLike(maybePromise) {
  97. return maybePromise && typeof maybePromise.then === 'function';
  98. }
  99. /**
  100. * Applies the function `eachFn` to each item in `arr`, in parallel.
  101. *
  102. * @param {array} arr an array of items to asynchronusly iterate over
  103. * @param {function} eachFn A function to call on each item of the array. The callback signature is `(item, callback)`, where the callback indicates iteration is complete.
  104. * @param {function} callback The callback called after every item has been iterated
  105. */
  106. function eachAsync(arr, eachFn, callback) {
  107. arr = arr || [];
  108. let idx = 0;
  109. let awaiting = 0;
  110. for (idx = 0; idx < arr.length; ++idx) {
  111. awaiting++;
  112. eachFn(arr[idx], eachCallback);
  113. }
  114. if (awaiting === 0) {
  115. callback();
  116. return;
  117. }
  118. function eachCallback(err) {
  119. awaiting--;
  120. if (err) {
  121. callback(err);
  122. return;
  123. }
  124. if (idx === arr.length && awaiting <= 0) {
  125. callback();
  126. }
  127. }
  128. }
  129. function eachAsyncSeries(arr, eachFn, callback) {
  130. arr = arr || [];
  131. let idx = 0;
  132. let awaiting = arr.length;
  133. if (awaiting === 0) {
  134. callback();
  135. return;
  136. }
  137. function eachCallback(err) {
  138. idx++;
  139. awaiting--;
  140. if (err) {
  141. callback(err);
  142. return;
  143. }
  144. if (idx === arr.length && awaiting <= 0) {
  145. callback();
  146. return;
  147. }
  148. eachFn(arr[idx], eachCallback);
  149. }
  150. eachFn(arr[idx], eachCallback);
  151. }
  152. function isUnifiedTopology(topology) {
  153. return topology.description != null;
  154. }
  155. function arrayStrictEqual(arr, arr2) {
  156. if (!Array.isArray(arr) || !Array.isArray(arr2)) {
  157. return false;
  158. }
  159. return arr.length === arr2.length && arr.every((elt, idx) => elt === arr2[idx]);
  160. }
  161. function tagsStrictEqual(tags, tags2) {
  162. const tagsKeys = Object.keys(tags);
  163. const tags2Keys = Object.keys(tags2);
  164. return tagsKeys.length === tags2Keys.length && tagsKeys.every(key => tags2[key] === tags[key]);
  165. }
  166. function errorStrictEqual(lhs, rhs) {
  167. if (lhs === rhs) {
  168. return true;
  169. }
  170. if ((lhs == null && rhs != null) || (lhs != null && rhs == null)) {
  171. return false;
  172. }
  173. if (lhs.constructor.name !== rhs.constructor.name) {
  174. return false;
  175. }
  176. if (lhs.message !== rhs.message) {
  177. return false;
  178. }
  179. return true;
  180. }
  181. function makeStateMachine(stateTable) {
  182. return function stateTransition(target, newState) {
  183. const legalStates = stateTable[target.s.state];
  184. if (legalStates && legalStates.indexOf(newState) < 0) {
  185. throw new TypeError(
  186. `illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`
  187. );
  188. }
  189. target.emit('stateChanged', target.s.state, newState);
  190. target.s.state = newState;
  191. };
  192. }
  193. function makeClientMetadata(options) {
  194. options = options || {};
  195. const metadata = {
  196. driver: {
  197. name: 'nodejs',
  198. version: require('../../package.json').version
  199. },
  200. os: {
  201. type: os.type(),
  202. name: process.platform,
  203. architecture: process.arch,
  204. version: os.release()
  205. },
  206. platform: `'Node.js ${process.version}, ${os.endianness} (${
  207. options.useUnifiedTopology ? 'unified' : 'legacy'
  208. })`
  209. };
  210. // support optionally provided wrapping driver info
  211. if (options.driverInfo) {
  212. if (options.driverInfo.name) {
  213. metadata.driver.name = `${metadata.driver.name}|${options.driverInfo.name}`;
  214. }
  215. if (options.driverInfo.version) {
  216. metadata.version = `${metadata.driver.version}|${options.driverInfo.version}`;
  217. }
  218. if (options.driverInfo.platform) {
  219. metadata.platform = `${metadata.platform}|${options.driverInfo.platform}`;
  220. }
  221. }
  222. if (options.appname) {
  223. // MongoDB requires the appname not exceed a byte length of 128
  224. const buffer = Buffer.from(options.appname);
  225. metadata.application = {
  226. name: buffer.length > 128 ? buffer.slice(0, 128).toString('utf8') : options.appname
  227. };
  228. }
  229. return metadata;
  230. }
  231. const noop = () => {};
  232. module.exports = {
  233. uuidV4,
  234. relayEvents,
  235. collationNotSupported,
  236. retrieveEJSON,
  237. retrieveKerberos,
  238. maxWireVersion,
  239. isPromiseLike,
  240. eachAsync,
  241. eachAsyncSeries,
  242. isUnifiedTopology,
  243. arrayStrictEqual,
  244. tagsStrictEqual,
  245. errorStrictEqual,
  246. makeStateMachine,
  247. makeClientMetadata,
  248. noop
  249. };