utils.js 6.8 KB

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