├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── dist ├── object_hash.js └── object_hash_test.js ├── gulpfile.js ├── index.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── readme.markdown └── test ├── blob.js ├── index.js ├── object-classes.js ├── old-crypto.js ├── replacer.js ├── types.js └── writeToStream.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import("eslint").Linter.Config} */ 2 | module.exports = { 3 | extends: "eslint:recommended", 4 | ignorePatterns: [ 5 | "dist", 6 | "coverage" 7 | ], 8 | env: { 9 | node: true, 10 | es2024: true 11 | }, 12 | overrides: [ 13 | { 14 | env: { 15 | mocha: true 16 | }, 17 | files: [ 18 | "test/**/*.js" 19 | ] 20 | } 21 | ], 22 | rules: { 23 | "curly": "error", 24 | "eqeqeq": "error", 25 | "new-cap": ["error", { capIsNew: false }], 26 | "no-caller": "error" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | run-tasks: 9 | name: Run other tasks 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 21 19 | check-latest: true 20 | cache: npm 21 | 22 | - name: Install dependencies 23 | run: npm ci 24 | 25 | - name: Run linter 26 | run: npm run lint 27 | 28 | - name: Run build 29 | run: npx gulp dist 30 | 31 | run-tests: 32 | name: Run tests on Node.js v${{ matrix.node }} 33 | runs-on: ubuntu-latest 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | node: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] 38 | steps: 39 | - name: Check out repository 40 | uses: actions/checkout@v4 41 | 42 | - name: Set up Node.js 43 | uses: actions/setup-node@v4 44 | with: 45 | node-version: ${{ matrix.node }} 46 | check-latest: true 47 | cache: npm 48 | 49 | - name: Install dependencies 50 | # Note: `npm ci` is faster and more reliable than `npm install` for CI, 51 | # but it is not compatible with older versions of Node.js that we still support. 52 | run: npm install 53 | 54 | - name: Run tests 55 | run: npx gulp test karma 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .monitor 3 | .*.swp 4 | .nodemonignore 5 | releases 6 | *.log 7 | *.err 8 | fleet.json 9 | public/browserify 10 | bin/*.json 11 | .bin 12 | build 13 | compile 14 | .lock-wscript 15 | node_modules/ 16 | coverage/ 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 object-hash contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /dist/object_hash.js: -------------------------------------------------------------------------------- 1 | !function(e){var t;"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):("undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.objectHash=e())}(function(){return function r(o,i,u){function s(n,e){if(!i[n]){if(!o[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(a)return a(n,!0);throw new Error("Cannot find module '"+n+"'")}e=i[n]={exports:{}};o[n][0].call(e.exports,function(e){var t=o[n][1][e];return s(t||e)},e,e.exports,r,o,i,u)}return i[n].exports}for(var a="function"==typeof require&&require,e=0;e>16),s((65280&n)>>8),s(255&n);return 2==r?s(255&(n=f(e.charAt(t))<<2|f(e.charAt(t+1))>>4)):1==r&&(s((n=f(e.charAt(t))<<10|f(e.charAt(t+1))<<4|f(e.charAt(t+2))>>2)>>8&255),s(255&n)),o},e.fromByteArray=function(e){var t,n,r,o,i=e.length%3,u="";function s(e){return"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(e)}for(t=0,r=e.length-i;t>18&63)+s(o>>12&63)+s(o>>6&63)+s(63&o);switch(i){case 1:u=(u+=s((n=e[e.length-1])>>2))+s(n<<4&63)+"==";break;case 2:u=(u=(u+=s((n=(e[e.length-2]<<8)+e[e.length-1])>>10))+s(n>>4&63))+s(n<<2&63)+"="}return u}}(void 0===f?this.base64js={}:f)}.call(this,e("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/base64-js/lib/b64.js","/node_modules/gulp-browserify/node_modules/base64-js/lib")},{buffer:3,lYpoI2:11}],3:[function(O,e,H){!function(e,n,f,r,h,p,g,y,w){var a=O("base64-js"),i=O("ieee754");function f(e,t,n){if(!(this instanceof f))return new f(e,t,n);var r,o,i,u,s=typeof e;if("base64"===t&&"string"==s)for(e=(u=e).trim?u.trim():u.replace(/^\s+|\s+$/g,"");e.length%4!=0;)e+="=";if("number"==s)r=j(e);else if("string"==s)r=f.byteLength(e,t);else{if("object"!=s)throw new Error("First argument needs to be a number, array or string.");r=j(e.length)}if(f._useTypedArrays?o=f._augment(new Uint8Array(r)):((o=this).length=r,o._isBuffer=!0),f._useTypedArrays&&"number"==typeof e.byteLength)o._set(e);else if(C(u=e)||f.isBuffer(u)||u&&"object"==typeof u&&"number"==typeof u.length)for(i=0;i>8,n=n%256,r.push(n),r.push(t);return r}(t),e,n,r)}function v(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;o>>0)):(t+1>>0),o}function _(e,t,n,r){if(r||(d("boolean"==typeof n,"missing or invalid endian"),d(null!=t,"missing offset"),d(t+1>>8*(r?i:1-i)}function l(e,t,n,r,o){o||(d(null!=t,"missing value"),d("boolean"==typeof r,"missing or invalid endian"),d(null!=n,"missing offset"),d(n+3>>8*(r?i:3-i)&255}function B(e,t,n,r,o){o||(d(null!=t,"missing value"),d("boolean"==typeof r,"missing or invalid endian"),d(null!=n,"missing offset"),d(n+1this.length&&(r=this.length);var o=(r=e.length-t=this.length))return this[e]},f.prototype.readUInt16LE=function(e,t){return o(this,e,!0,t)},f.prototype.readUInt16BE=function(e,t){return o(this,e,!1,t)},f.prototype.readUInt32LE=function(e,t){return u(this,e,!0,t)},f.prototype.readUInt32BE=function(e,t){return u(this,e,!1,t)},f.prototype.readInt8=function(e,t){if(t||(d(null!=e,"missing offset"),d(e=this.length))return 128&this[e]?-1*(255-this[e]+1):this[e]},f.prototype.readInt16LE=function(e,t){return _(this,e,!0,t)},f.prototype.readInt16BE=function(e,t){return _(this,e,!1,t)},f.prototype.readInt32LE=function(e,t){return E(this,e,!0,t)},f.prototype.readInt32BE=function(e,t){return E(this,e,!1,t)},f.prototype.readFloatLE=function(e,t){return I(this,e,!0,t)},f.prototype.readFloatBE=function(e,t){return I(this,e,!1,t)},f.prototype.readDoubleLE=function(e,t){return A(this,e,!0,t)},f.prototype.readDoubleBE=function(e,t){return A(this,e,!1,t)},f.prototype.writeUInt8=function(e,t,n){n||(d(null!=e,"missing value"),d(null!=t,"missing offset"),d(t=this.length||(this[t]=e)},f.prototype.writeUInt16LE=function(e,t,n){s(this,e,t,!0,n)},f.prototype.writeUInt16BE=function(e,t,n){s(this,e,t,!1,n)},f.prototype.writeUInt32LE=function(e,t,n){l(this,e,t,!0,n)},f.prototype.writeUInt32BE=function(e,t,n){l(this,e,t,!1,n)},f.prototype.writeInt8=function(e,t,n){n||(d(null!=e,"missing value"),d(null!=t,"missing offset"),d(t=this.length||(0<=e?this.writeUInt8(e,t,n):this.writeUInt8(255+e+1,t,n))},f.prototype.writeInt16LE=function(e,t,n){B(this,e,t,!0,n)},f.prototype.writeInt16BE=function(e,t,n){B(this,e,t,!1,n)},f.prototype.writeInt32LE=function(e,t,n){L(this,e,t,!0,n)},f.prototype.writeInt32BE=function(e,t,n){L(this,e,t,!1,n)},f.prototype.writeFloatLE=function(e,t,n){U(this,e,t,!0,n)},f.prototype.writeFloatBE=function(e,t,n){U(this,e,t,!1,n)},f.prototype.writeDoubleLE=function(e,t,n){x(this,e,t,!0,n)},f.prototype.writeDoubleBE=function(e,t,n){x(this,e,t,!1,n)},f.prototype.fill=function(e,t,n){if(t=t||0,n=n||this.length,d("number"==typeof(e="string"==typeof(e=e||0)?e.charCodeAt(0):e)&&!isNaN(e),"value is not a number"),d(t<=n,"end < start"),n!==t&&0!==this.length){d(0<=t&&t"},f.prototype.toArrayBuffer=function(){if("undefined"==typeof Uint8Array)throw new Error("Buffer.toArrayBuffer not supported in this browser");if(f._useTypedArrays)return new f(this).buffer;for(var e=new Uint8Array(this.length),t=0,n=e.length;t=t.length||o>=e.length);o++)t[o+n]=e[o];return o}function N(e){try{return decodeURIComponent(e)}catch(e){return String.fromCharCode(65533)}}function Y(e,t){d("number"==typeof e,"cannot write a non-number as a number"),d(0<=e,"specified a negative value for writing an unsigned value"),d(e<=t,"value is larger than maximum value for type"),d(Math.floor(e)===e,"value has a fractional component")}function F(e,t,n){d("number"==typeof e,"cannot write a non-number as a number"),d(e<=t,"value larger than maximum allowed value"),d(n<=e,"value smaller than minimum allowed value"),d(Math.floor(e)===e,"value has a fractional component")}function D(e,t,n){d("number"==typeof e,"cannot write a non-number as a number"),d(e<=t,"value larger than maximum allowed value"),d(n<=e,"value smaller than minimum allowed value")}function d(e,t){if(!e)throw new Error(t||"Failed assertion")}f._augment=function(e){return e._isBuffer=!0,e._get=e.get,e._set=e.set,e.get=t.get,e.set=t.set,e.write=t.write,e.toString=t.toString,e.toLocaleString=t.toString,e.toJSON=t.toJSON,e.copy=t.copy,e.slice=t.slice,e.readUInt8=t.readUInt8,e.readUInt16LE=t.readUInt16LE,e.readUInt16BE=t.readUInt16BE,e.readUInt32LE=t.readUInt32LE,e.readUInt32BE=t.readUInt32BE,e.readInt8=t.readInt8,e.readInt16LE=t.readInt16LE,e.readInt16BE=t.readInt16BE,e.readInt32LE=t.readInt32LE,e.readInt32BE=t.readInt32BE,e.readFloatLE=t.readFloatLE,e.readFloatBE=t.readFloatBE,e.readDoubleLE=t.readDoubleLE,e.readDoubleBE=t.readDoubleBE,e.writeUInt8=t.writeUInt8,e.writeUInt16LE=t.writeUInt16LE,e.writeUInt16BE=t.writeUInt16BE,e.writeUInt32LE=t.writeUInt32LE,e.writeUInt32BE=t.writeUInt32BE,e.writeInt8=t.writeInt8,e.writeInt16LE=t.writeInt16LE,e.writeInt16BE=t.writeInt16BE,e.writeInt32LE=t.writeInt32LE,e.writeInt32BE=t.writeInt32BE,e.writeFloatLE=t.writeFloatLE,e.writeFloatBE=t.writeFloatBE,e.writeDoubleLE=t.writeDoubleLE,e.writeDoubleBE=t.writeDoubleBE,e.fill=t.fill,e.inspect=t.inspect,e.toArrayBuffer=t.toArrayBuffer,e}}.call(this,O("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},O("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/buffer/index.js","/node_modules/gulp-browserify/node_modules/buffer")},{"base64-js":2,buffer:3,ieee754:10,lYpoI2:11}],4:[function(c,d,e){!function(e,t,a,n,r,o,i,u,s){var a=c("buffer").Buffer,f=4,l=new a(f);l.fill(0);d.exports={hash:function(e,t,n,r){for(var o=t(function(e,t){e.length%f!=0&&(n=e.length+(f-e.length%f),e=a.concat([e,l],n));for(var n,r=[],o=t?e.readInt32BE:e.readInt32LE,i=0;is?t=e(t):t.length>5]|=128<>>9<<4)]=t;for(var n=1732584193,r=-271733879,o=-1732584194,i=271733878,u=0;u>>32-o,n)}function c(e,t,n,r,o,i,u){return s(t&n|~t&r,e,t,o,i,u)}function d(e,t,n,r,o,i,u){return s(t&r|n&~r,e,t,o,i,u)}function h(e,t,n,r,o,i,u){return s(t^n^r,e,t,o,i,u)}function p(e,t,n,r,o,i,u){return s(n^(t|~r),e,t,o,i,u)}function g(e,t){var n=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(n>>16)<<16|65535&n}b.exports=function(e){return t.hash(e,n,16)}}.call(this,w("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},w("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/md5.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],7:[function(e,l,t){!function(e,t,n,r,o,i,u,s,f){var a;l.exports=a||function(e){for(var t,n=new Array(e),r=0;r>>((3&r)<<3)&255;return n}}.call(this,e("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/rng.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{buffer:3,lYpoI2:11}],8:[function(c,d,e){!function(e,t,n,r,o,s,a,f,l){var i=c("./helpers");function u(l,c){l[c>>5]|=128<<24-c%32,l[15+(c+64>>9<<4)]=c;for(var e,t,n,r=Array(80),o=1732584193,i=-271733879,u=-1732584194,s=271733878,d=-1009589776,h=0;h>16)+(t>>16)+(n>>16)<<16|65535&n}function v(e,t){return e<>>32-t}d.exports=function(e){return i.hash(e,u,20,!0)}}.call(this,c("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},c("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/sha.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],9:[function(c,d,e){!function(e,t,n,r,u,s,a,f,l){function b(e,t){var n=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(n>>16)<<16|65535&n}function o(e,l){var c,d=new Array(1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298),t=new Array(1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225),n=new Array(64);e[l>>5]|=128<<24-l%32,e[15+(l+64>>9<<4)]=l;for(var r,o,h=0;h>>t|e<<32-t},v=function(e,t){return e>>>t};d.exports=function(e){return i.hash(e,o,32,!0)}}.call(this,c("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},c("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/sha256.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],10:[function(e,t,f){!function(e,t,n,r,o,i,u,s,a){f.read=function(e,t,n,r,o){var i,u,l=8*o-r-1,c=(1<>1,s=-7,a=n?o-1:0,f=n?-1:1,o=e[t+a];for(a+=f,i=o&(1<<-s)-1,o>>=-s,s+=l;0>=-s,s+=r;0>1,d=23===r?Math.pow(2,-24)-Math.pow(2,-77):0,f=n?0:c-1,h=n?1:-1,c=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(i=isNaN(t)?1:0,o=s):(o=Math.floor(Math.log(t)/Math.LN2),t*(n=Math.pow(2,-o))<1&&(o--,n*=2),2<=(t+=1<=o+a?d/n:d*Math.pow(2,1-a))*n&&(o++,n/=2),s<=o+a?(i=0,o=s):1<=o+a?(i=(t*n-1)*Math.pow(2,r),o+=a):(i=t*Math.pow(2,a-1)*Math.pow(2,r),o=0));8<=r;e[l+f]=255&i,f+=h,i/=256,r-=8);for(o=o<", type, " -> ", "_" + type); 189 | 190 | return this['_' + type](value); 191 | }, 192 | _object: function(object) { 193 | var pattern = (/\[object (.*)\]/i); 194 | var objString = Object.prototype.toString.call(object); 195 | var objType = pattern.exec(objString); 196 | if (!objType) { // object type did not match [object ...] 197 | objType = 'unknown:[' + objString + ']'; 198 | } else { 199 | objType = objType[1]; // take only the class name 200 | } 201 | 202 | objType = objType.toLowerCase(); 203 | 204 | var objectNumber = null; 205 | 206 | if ((objectNumber = context.indexOf(object)) >= 0) { 207 | return this.dispatch('[CIRCULAR:' + objectNumber + ']'); 208 | } else { 209 | context.push(object); 210 | } 211 | 212 | if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(object)) { 213 | write('buffer:'); 214 | return write(object); 215 | } 216 | 217 | if(objType !== 'object' && objType !== 'function' && objType !== 'asyncfunction') { 218 | if(this['_' + objType]) { 219 | this['_' + objType](object); 220 | } else if (options.ignoreUnknown) { 221 | return write('[' + objType + ']'); 222 | } else { 223 | throw new Error('Unknown object type "' + objType + '"'); 224 | } 225 | }else{ 226 | var keys = Object.keys(object); 227 | if (options.unorderedObjects) { 228 | keys = keys.sort(); 229 | } 230 | // Make sure to incorporate special properties, so 231 | // Types with different prototypes will produce 232 | // a different hash and objects derived from 233 | // different functions (`new Foo`, `new Bar`) will 234 | // produce different hashes. 235 | // We never do this for native functions since some 236 | // seem to break because of that. 237 | if (options.respectType !== false && !isNativeFunction(object)) { 238 | keys.splice(0, 0, 'prototype', '__proto__', 'constructor'); 239 | } 240 | 241 | if (options.excludeKeys) { 242 | keys = keys.filter(function(key) { return !options.excludeKeys(key); }); 243 | } 244 | 245 | write('object:' + keys.length + ':'); 246 | var self = this; 247 | return keys.forEach(function(key){ 248 | self.dispatch(key); 249 | write(':'); 250 | if(!options.excludeValues) { 251 | self.dispatch(object[key]); 252 | } 253 | write(','); 254 | }); 255 | } 256 | }, 257 | _array: function(arr, unordered){ 258 | unordered = typeof unordered !== 'undefined' ? unordered : 259 | options.unorderedArrays !== false; // default to options.unorderedArrays 260 | 261 | var self = this; 262 | write('array:' + arr.length + ':'); 263 | if (!unordered || arr.length <= 1) { 264 | return arr.forEach(function(entry) { 265 | return self.dispatch(entry); 266 | }); 267 | } 268 | 269 | // the unordered case is a little more complicated: 270 | // since there is no canonical ordering on objects, 271 | // i.e. {a:1} < {a:2} and {a:1} > {a:2} are both false, 272 | // we first serialize each entry using a PassThrough stream 273 | // before sorting. 274 | // also: we can’t use the same context array for all entries 275 | // since the order of hashing should *not* matter. instead, 276 | // we keep track of the additions to a copy of the context array 277 | // and add all of them to the global context array when we’re done 278 | var contextAdditions = []; 279 | var entries = arr.map(function(entry) { 280 | var strm = new PassThrough(); 281 | var localContext = context.slice(); // make copy 282 | var hasher = typeHasher(options, strm, localContext); 283 | hasher.dispatch(entry); 284 | // take only what was added to localContext and append it to contextAdditions 285 | contextAdditions = contextAdditions.concat(localContext.slice(context.length)); 286 | return strm.read().toString(); 287 | }); 288 | context = context.concat(contextAdditions); 289 | entries.sort(); 290 | return this._array(entries, false); 291 | }, 292 | _date: function(date){ 293 | return write('date:' + date.toJSON()); 294 | }, 295 | _symbol: function(sym){ 296 | return write('symbol:' + sym.toString()); 297 | }, 298 | _error: function(err){ 299 | return write('error:' + err.toString()); 300 | }, 301 | _boolean: function(bool){ 302 | return write('bool:' + bool.toString()); 303 | }, 304 | _string: function(string){ 305 | write('string:' + string.length + ':'); 306 | write(string.toString()); 307 | }, 308 | _function: function(fn){ 309 | write('fn:'); 310 | if (isNativeFunction(fn)) { 311 | this.dispatch('[native]'); 312 | } else { 313 | this.dispatch(fn.toString()); 314 | } 315 | 316 | if (options.respectFunctionNames !== false) { 317 | // Make sure we can still distinguish native functions 318 | // by their name, otherwise String and Function will 319 | // have the same hash 320 | this.dispatch("function-name:" + String(fn.name)); 321 | } 322 | 323 | if (options.respectFunctionProperties) { 324 | this._object(fn); 325 | } 326 | }, 327 | _number: function(number){ 328 | return write('number:' + number.toString()); 329 | }, 330 | _xml: function(xml){ 331 | return write('xml:' + xml.toString()); 332 | }, 333 | _null: function() { 334 | return write('Null'); 335 | }, 336 | _undefined: function() { 337 | return write('Undefined'); 338 | }, 339 | _regexp: function(regex){ 340 | return write('regex:' + regex.toString()); 341 | }, 342 | _uint8array: function(arr){ 343 | write('uint8array:'); 344 | return this.dispatch(Array.prototype.slice.call(arr)); 345 | }, 346 | _uint8clampedarray: function(arr){ 347 | write('uint8clampedarray:'); 348 | return this.dispatch(Array.prototype.slice.call(arr)); 349 | }, 350 | _int8array: function(arr){ 351 | write('int8array:'); 352 | return this.dispatch(Array.prototype.slice.call(arr)); 353 | }, 354 | _uint16array: function(arr){ 355 | write('uint16array:'); 356 | return this.dispatch(Array.prototype.slice.call(arr)); 357 | }, 358 | _int16array: function(arr){ 359 | write('int16array:'); 360 | return this.dispatch(Array.prototype.slice.call(arr)); 361 | }, 362 | _uint32array: function(arr){ 363 | write('uint32array:'); 364 | return this.dispatch(Array.prototype.slice.call(arr)); 365 | }, 366 | _int32array: function(arr){ 367 | write('int32array:'); 368 | return this.dispatch(Array.prototype.slice.call(arr)); 369 | }, 370 | _float32array: function(arr){ 371 | write('float32array:'); 372 | return this.dispatch(Array.prototype.slice.call(arr)); 373 | }, 374 | _float64array: function(arr){ 375 | write('float64array:'); 376 | return this.dispatch(Array.prototype.slice.call(arr)); 377 | }, 378 | _arraybuffer: function(arr){ 379 | write('arraybuffer:'); 380 | return this.dispatch(new Uint8Array(arr)); 381 | }, 382 | _url: function(url) { 383 | return write('url:' + url.toString(), 'utf8'); 384 | }, 385 | _map: function(map) { 386 | write('map:'); 387 | var arr = Array.from(map); 388 | return this._array(arr, options.unorderedSets !== false); 389 | }, 390 | _set: function(set) { 391 | write('set:'); 392 | var arr = Array.from(set); 393 | return this._array(arr, options.unorderedSets !== false); 394 | }, 395 | _file: function(file) { 396 | write('file:'); 397 | return this.dispatch([file.name, file.size, file.type, file.lastModfied]); 398 | }, 399 | _blob: function() { 400 | if (options.ignoreUnknown) { 401 | return write('[blob]'); 402 | } 403 | 404 | throw Error('Hashing Blob objects is currently not supported\n' + 405 | '(see https://github.com/puleos/object-hash/issues/26)\n' + 406 | 'Use "options.replacer" or "options.ignoreUnknown"\n'); 407 | }, 408 | _domwindow: function() { return write('domwindow'); }, 409 | _bigint: function(number){ 410 | return write('bigint:' + number.toString()); 411 | }, 412 | /* Node.js standard native objects */ 413 | _process: function() { return write('process'); }, 414 | _timer: function() { return write('timer'); }, 415 | _pipe: function() { return write('pipe'); }, 416 | _tcp: function() { return write('tcp'); }, 417 | _udp: function() { return write('udp'); }, 418 | _tty: function() { return write('tty'); }, 419 | _statwatcher: function() { return write('statwatcher'); }, 420 | _securecontext: function() { return write('securecontext'); }, 421 | _connection: function() { return write('connection'); }, 422 | _zlib: function() { return write('zlib'); }, 423 | _context: function() { return write('context'); }, 424 | _nodescript: function() { return write('nodescript'); }, 425 | _httpparser: function() { return write('httpparser'); }, 426 | _dataview: function() { return write('dataview'); }, 427 | _signal: function() { return write('signal'); }, 428 | _fsevent: function() { return write('fsevent'); }, 429 | _tlswrap: function() { return write('tlswrap'); }, 430 | }; 431 | } 432 | 433 | // Mini-implementation of stream.PassThrough 434 | // We are far from having need for the full implementation, and we can 435 | // make assumptions like "many writes, then only one final read" 436 | // and we can ignore encoding specifics 437 | function PassThrough() { 438 | return { 439 | buf: '', 440 | 441 | write: function(b) { 442 | this.buf += b; 443 | }, 444 | 445 | end: function(b) { 446 | this.buf += b; 447 | }, 448 | 449 | read: function() { 450 | return this.buf; 451 | } 452 | }; 453 | } 454 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Dec 10 2015 03:01:27 GMT+0100 (CET) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'dist/*.js' 19 | ], 20 | 21 | 22 | // list of files to exclude 23 | exclude: [ 24 | ], 25 | 26 | 27 | // preprocess matching files before serving them to the browser 28 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 29 | preprocessors: { 30 | }, 31 | 32 | 33 | // test results reporter to use 34 | // possible values: 'dots', 'progress' 35 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 36 | reporters: ['progress'], 37 | 38 | 39 | // web server port 40 | port: 9876, 41 | 42 | 43 | // enable / disable colors in the output (reporters and logs) 44 | colors: true, 45 | 46 | 47 | // level of logging 48 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 49 | logLevel: config.LOG_INFO, 50 | 51 | 52 | // enable / disable watching file and executing tests whenever any file changes 53 | autoWatch: true, 54 | 55 | 56 | // start these browsers 57 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 58 | browsers: ['ChromeHeadless'], 59 | 60 | 61 | // Continuous Integration mode 62 | // if true, Karma captures browsers, runs the tests and exits 63 | singleRun: false, 64 | 65 | // Concurrency level 66 | // how many browser should be started simultanous 67 | concurrency: Infinity 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "object-hash", 3 | "version": "3.0.0", 4 | "description": "Generate hashes from javascript objects in node and the browser.", 5 | "homepage": "https://github.com/puleos/object-hash", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/puleos/object-hash" 9 | }, 10 | "keywords": [ 11 | "object", 12 | "hash", 13 | "sha1", 14 | "md5" 15 | ], 16 | "bugs": { 17 | "url": "https://github.com/puleos/object-hash/issues" 18 | }, 19 | "scripts": { 20 | "lint": "eslint .", 21 | "test": "node ./node_modules/.bin/mocha test", 22 | "prepublishOnly": "gulp dist" 23 | }, 24 | "author": "Scott Puleo ", 25 | "files": [ 26 | "index.js", 27 | "dist/object_hash.js" 28 | ], 29 | "license": "MIT", 30 | "devDependencies": { 31 | "browserify": "^17.0.0", 32 | "eslint": "^8.57.0", 33 | "gulp": "^4.0.0", 34 | "gulp-browserify": "^0.5.1", 35 | "gulp-coveralls": "^0.1.4", 36 | "gulp-istanbul": "^1.1.3", 37 | "gulp-mocha": "^5.0.0", 38 | "gulp-rename": "^2.0.0", 39 | "gulp-replace": "^1.0.0", 40 | "gulp-uglify": "^3.0.0", 41 | "karma": "^4.2.0", 42 | "karma-chrome-launcher": "^2.2.0", 43 | "karma-mocha": "^1.3.0", 44 | "mocha": "^7.2.0" 45 | }, 46 | "engines": { 47 | "node": ">= 6" 48 | }, 49 | "main": "./index.js", 50 | "browser": "./dist/object_hash.js" 51 | } 52 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | # object-hash 2 | 3 | Generate hashes from objects and values in node and the browser. Uses node.js 4 | crypto module for hashing. Supports SHA1 and many others (depending on the platform) 5 | as well as custom streams (e.g. CRC32). 6 | 7 | [![NPM](https://nodei.co/npm/object-hash.png?downloads=true&downloadRank=true)](https://www.npmjs.com/package/object-hash) 8 | 9 | [![CI](https://github.com/puleos/object-hash/actions/workflows/main.yml/badge.svg)](https://github.com/puleos/object-hash/actions/workflows/main.yml) 10 | [![Coverage Status](https://coveralls.io/repos/puleos/object-hash/badge.svg?branch=master&service=github)](https://coveralls.io/github/puleos/object-hash?branch=master) 11 | 12 | * Hash values of any type. 13 | * Supports a keys only option for grouping similar objects with different values. 14 | 15 | ```js 16 | var hash = require('object-hash'); 17 | 18 | hash({foo: 'bar'}) // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9' 19 | hash([1, 2, 2.718, 3.14159]) // => '136b9b88375971dff9f1af09d7356e3e04281951' 20 | ``` 21 | 22 | ## Versioning Disclaimer 23 | 24 | Starting with version `1.1.8` (released April 2017), new versions will consider 25 | the exact returned hash part of the API contract, i.e. changes that will affect 26 | hash values will be considered `semver-major`. Previous versions may violate 27 | that expectation. 28 | 29 | For more information, see [this discussion](https://github.com/puleos/object-hash/issues/30). 30 | 31 | ## hash(value, options) 32 | 33 | Generate a hash from any object or type. Defaults to sha1 with hex encoding. 34 | 35 | * `algorithm` hash algo to be used: 'sha1', 'md5', 'passthrough'. default: sha1 36 | * This supports the algorithms returned by `crypto.getHashes()`. Note that the default of SHA-1 is not considered secure, and a stronger algorithm should be used if a cryptographical hash is desired. 37 | * This also supports the `passthrough` algorith, which will return the information that would otherwise have been hashed. 38 | * `excludeValues` {true|false} hash object keys, values ignored. default: false 39 | * `encoding` hash encoding, supports 'buffer', 'hex', 'binary', 'base64'. default: hex 40 | * `ignoreUnknown` {true|*false} ignore unknown object types. default: false 41 | * `replacer` optional function that replaces values before hashing. default: accept all values 42 | * `respectFunctionProperties` {true|false} Whether properties on functions are considered when hashing. default: true 43 | * `respectFunctionNames` {true|false} consider `name` property of functions for hashing. default: true 44 | * `respectType` {true|false} Whether special type attributes (`.prototype`, `.__proto__`, `.constructor`) 45 | are hashed. default: true 46 | * `unorderedArrays` {true|false} Sort all arrays before hashing. Note that this affects *all* collections, 47 | i.e. including typed arrays, Sets, Maps, etc. default: false 48 | * `unorderedSets` {true|false} Sort `Set` and `Map` instances before hashing, i.e. make 49 | `hash(new Set([1, 2])) == hash(new Set([2, 1]))` return `true`. default: true 50 | * `unorderedObjects` {true|false} Sort objects before hashing, i.e. make `hash({ x: 1, y: 2 }) === hash({ y: 2, x: 1 })`. default: true 51 | * `excludeKeys` optional function for excluding specific key(s) from hashing, if true is returned then exclude from hash. default: include all keys 52 | 53 | ## hash.sha1(value) 54 | 55 | Hash using the sha1 algorithm. 56 | 57 | Note that SHA-1 is not considered secure, and a stronger algorithm should be used if a cryptographical hash is desired. 58 | 59 | *Sugar method, equivalent to* `hash(value, {algorithm: 'sha1'})` 60 | 61 | ## hash.keys(value) 62 | 63 | Hash object keys using the sha1 algorithm, values ignored. 64 | 65 | *Sugar method, equivalent to* `hash(value, {excludeValues: true})` 66 | 67 | ## hash.MD5(value) 68 | 69 | Hash using the md5 algorithm. 70 | 71 | Note that the MD5 algorithm is not considered secure, and a stronger algorithm should be used if a cryptographical hash is desired. 72 | 73 | *Sugar method, equivalent to* `hash(value, {algorithm: 'md5'})` 74 | 75 | ## hash.keysMD5(value) 76 | 77 | Hash object keys using the md5 algorithm, values ignored. 78 | 79 | Note that the MD5 algorithm is not considered secure, and a stronger algorithm should be used if a cryptographical hash is desired. 80 | 81 | *Sugar method, equivalent to* `hash(value, {algorithm: 'md5', excludeValues: true})` 82 | 83 | ## hash.writeToStream(value, [options,] stream) 84 | 85 | Write the information that would otherwise have been hashed to a stream, e.g.: 86 | 87 | ```js 88 | hash.writeToStream({foo: 'bar', a: 42}, {respectType: false}, process.stdout) 89 | // => e.g. 'object:a:number:42foo:string:bar' 90 | ``` 91 | 92 | ## Installation 93 | 94 | node: 95 | 96 | ```js 97 | npm install object-hash 98 | ``` 99 | 100 | browser: */dist/object_hash.js* 101 | 102 | ```html 103 | 104 | 105 | 110 | ``` 111 | 112 | ## Example usage 113 | 114 | ```js 115 | var hash = require('object-hash'); 116 | 117 | var peter = { name: 'Peter', stapler: false, friends: ['Joanna', 'Michael', 'Samir'] }; 118 | var michael = { name: 'Michael', stapler: false, friends: ['Peter', 'Samir'] }; 119 | var bob = { name: 'Bob', stapler: true, friends: [] }; 120 | 121 | /*** 122 | * sha1 hex encoding (default) 123 | */ 124 | hash(peter); 125 | // 14fa461bf4b98155e82adc86532938553b4d33a9 126 | hash(michael); 127 | // 4b2b30e27699979ce46714253bc2213010db039c 128 | hash(bob); 129 | // 38d96106bc8ef3d8bd369b99bb6972702c9826d5 130 | 131 | /*** 132 | * hash object keys, values ignored 133 | */ 134 | hash(peter, { excludeValues: true }); 135 | // 48f370a772c7496f6c9d2e6d92e920c87dd00a5c 136 | hash(michael, { excludeValues: true }); 137 | // 48f370a772c7496f6c9d2e6d92e920c87dd00a5c 138 | hash.keys(bob); 139 | // 48f370a772c7496f6c9d2e6d92e920c87dd00a5c 140 | 141 | /*** 142 | * hash object, ignore specific key(s) 143 | */ 144 | hash(peter, { excludeKeys: function(key) { 145 | if ( key === 'friends') { 146 | return true; 147 | } 148 | return false; 149 | } 150 | }); 151 | // 66b7d7e64871aa9fda1bdc8e88a28df797648d80 152 | 153 | /*** 154 | * md5 base64 encoding 155 | */ 156 | hash(peter, { algorithm: 'md5', encoding: 'base64' }); 157 | // 6rkWaaDiG3NynWw4svGH7g== 158 | hash(michael, { algorithm: 'md5', encoding: 'base64' }); 159 | // djXaWpuWVJeOF8Sb6SFFNg== 160 | hash(bob, { algorithm: 'md5', encoding: 'base64' }); 161 | // lFzkw/IJ8/12jZI0rQeS3w== 162 | ``` 163 | 164 | ## Legacy Browser Support 165 | 166 | IE <= 8 and Opera <= 11 support dropped in version 0.3.0. If you require 167 | legacy browser support you must either use an ES5 shim or use version 0.2.5 168 | of this module. 169 | 170 | ## Development 171 | 172 | ```sh-session 173 | git clone https://github.com/puleos/object-hash 174 | ``` 175 | 176 | ## Node Docker Wrapper 177 | 178 | If you want to stand this up in a docker container, you should take at look 179 | at the [![node-object-hash](https://github.com/bean5/node-object-hash)](https://github.com/bean5/node-object-hash) project. 180 | 181 | ### Package scripts 182 | 183 | * `gulp watch` (default) watch files, test and lint on change/add 184 | * `gulp test` unit tests 185 | * `gulp karma` browser unit tests 186 | * `npm run lint` linter 187 | * `gulp dist` create browser version in /dist 188 | 189 | ## License 190 | 191 | MIT 192 | 193 | ## Changelog 194 | 195 | ### v3.0.0 196 | 197 | A change was introduced to the hash in order to differentiate signed and unsigned typed arrays. 198 | As per our semantic versioning guarantee, this was considered a breaking change. 199 | 200 | ### v2.0.0 201 | 202 | Only Node.js versions `>= 6.0.0` are being tested in CI now. 203 | No other breaking changes were introduced. 204 | -------------------------------------------------------------------------------- /test/blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var hash = require('../index'); 5 | var validSha1 = /^[0-9a-f]{40}$/i; 6 | 7 | if (typeof Blob !== 'undefined') { 8 | describe('hash()ing Blob objects', function() { 9 | var blob; 10 | before('create blob', function() { 11 | try { 12 | blob = new Blob(['ABC']); 13 | } catch(e) { 14 | // https://github.com/ariya/phantomjs/issues/11013 15 | if (!e.message.match(/'\[object BlobConstructor\]' is not a constructor/)) { 16 | throw e; 17 | } 18 | /* global WebKitBlobBuilder */ 19 | var builder = new WebKitBlobBuilder(); 20 | builder.append('ABC'); 21 | blob = builder.getBlob(); 22 | } 23 | }); 24 | 25 | it('should throw when trying to hash a blob', function() { 26 | assert.throws(function() { 27 | hash(blob); 28 | }, /not supported/); 29 | 30 | assert.throws(function() { 31 | hash({abcdef: blob}); 32 | }, /not supported/); 33 | }); 34 | 35 | it('should not throw when trying to hash a blob with ignoreUnknown', function() { 36 | var opt = {ignoreUnknown: true}; 37 | 38 | assert.ok(validSha1.test(hash(blob, opt)), 'ignore Blob'); 39 | assert.ok(validSha1.test(hash({abcdef: blob}, opt)), 'ignore Blob'); 40 | }); 41 | }); 42 | 43 | if (typeof File !== 'undefined') { 44 | describe('hashing Blob() objects', function() { 45 | it('should hash the same file the same way', function() { 46 | var hash1 = hash(new File(new Uint8Array([1,2,3]), 'foo', { 47 | type: 'application/octet-stream', 48 | lastModified: 100000 49 | })); 50 | var hash2 = hash(new File(new Uint8Array([1,2,3]), 'foo', { 51 | type: 'application/octet-stream', 52 | lastModified: 100000 53 | })); 54 | assert.strictEqual(hash1, hash2); 55 | assert.ok(validSha1.test(hash1)); 56 | }); 57 | it('should hash different files differently', function() { 58 | var hash1 = hash(new File(new Uint8Array([1,2,3]), 'foo', { 59 | type: 'application/octet-stream', 60 | lastModified: 100000 61 | })); 62 | var hash2 = hash(new File(new Uint8Array([1,2,3]), 'bar', { 63 | type: 'application/octet-stream', 64 | lastModified: 100000 65 | })); 66 | assert.notStrictEqual(hash1, hash2); 67 | assert.ok(validSha1.test(hash1)); 68 | }); 69 | it('should ignore file content', function() { 70 | var hash1 = hash(new File(new Uint8Array([1,2,4]), 'foo', { 71 | type: 'application/octet-stream', 72 | lastModified: 100000 73 | })); 74 | var hash2 = hash(new File(new Uint8Array([1,2,3]), 'foo', { 75 | type: 'application/octet-stream', 76 | lastModified: 100000 77 | })); 78 | assert.strictEqual(hash1, hash2); 79 | assert.ok(validSha1.test(hash1)); 80 | }); 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var crypto = require('crypto'); 5 | var hash = require('../index'); 6 | var validSha1 = /^[0-9a-f]{40}$/i; 7 | 8 | describe('hash', function() { 9 | it('throws when nothing to hash', function () { 10 | assert.throws(hash, 'no arguments'); 11 | assert.throws(function() { 12 | hash(undefined, {algorithm: 'md5'}); 13 | }, 'undefined'); 14 | }); 15 | 16 | it('throws when passed an invalid options', function() { 17 | assert.throws(function() { 18 | hash({foo: 'bar'}, {algorithm: 'shalala'}); 19 | }, 'bad algorithm'); 20 | assert.throws(function() { 21 | hash({foo: 'bar'}, {encoding: 'base16'}); 22 | }, 'bad encoding'); 23 | }); 24 | 25 | it('copies options rather than mutating', function() { 26 | var options = { 27 | algorithm: 'MD5', 28 | encoding: 'HEX' 29 | } 30 | 31 | hash({foo: 'bar'}, options) 32 | 33 | assert.deepEqual(options, { 34 | algorithm: 'MD5', 35 | encoding: 'HEX' 36 | }, 'source options have neither been modified nor added to') 37 | }); 38 | 39 | it('hashes a simple object', function() { 40 | assert.ok(validSha1.test(hash({foo: 'bar', bar: 'baz'})), 'hash object'); 41 | }); 42 | 43 | if (typeof Buffer !== 'undefined') { 44 | it('can return buffers', function() { 45 | assert.ok(Buffer.isBuffer(hash({foo: 'bar', bar: 'baz'}, {encoding: 'buffer'})), 'hash object'); 46 | }); 47 | } 48 | 49 | it('hashes identical objects with different key ordering', function() { 50 | var hash1 = hash({foo: 'bar', bar: 'baz'}); 51 | var hash2 = hash({bar: 'baz', foo: 'bar'}); 52 | var hash3 = hash({bar: 'foo', foo: 'baz'}); 53 | assert.equal(hash1, hash2, 'hashes are equal'); 54 | assert.notEqual(hash1, hash3, 'different objects not equal'); 55 | }); 56 | 57 | it('respects object key ordering when unorderedObjects = false', function() { 58 | var hash1 = hash({foo: 'bar', bar: 'baz'}, { unorderedObjects: false }); 59 | var hash2 = hash({bar: 'baz', foo: 'bar'}, { unorderedObjects: false }); 60 | assert.notEqual(hash1, hash2, 'hashes are not equal'); 61 | }); 62 | 63 | it('hashes only object keys when excludeValues option is set', function() { 64 | var hash1 = hash({foo: false, bar: 'OK'}, { excludeValues: true }); 65 | var hash2 = hash({foo: true, bar: 'NO'}, { excludeValues: true }); 66 | var hash3 = hash({foo: true, bar: 'OK', baz: false}, { excludeValues: true }); 67 | assert.equal(hash1, hash2, 'values not in hash digest'); 68 | assert.notEqual(hash1, hash3, 'different keys not equal'); 69 | }); 70 | 71 | it('array values are hashed', function() { 72 | var hash1 = hash({foo: ['bar', 'baz'], bax: true }); 73 | var hash2 = hash({foo: ['baz', 'bar'], bax: true }); 74 | assert.notEqual(hash1, hash2, 'different array orders are unique'); 75 | }); 76 | 77 | it('nested object values are hashed', function() { 78 | var hash1 = hash({foo: {bar: true, bax: 1}}); 79 | var hash2 = hash({foo: {bar: true, bax: 1}}); 80 | var hash3 = hash({foo: {bar: false, bax: 1}}); 81 | assert.equal(hash1, hash2, 'hashes are equal'); 82 | assert.notEqual(hash1, hash3, 'different objects not equal'); 83 | }); 84 | 85 | it('sugar methods should be equivalent', function() { 86 | var obj = {foo: 'bar', baz: true}; 87 | assert.equal(hash.keys(obj), hash(obj, {excludeValues: true}), 'keys'); 88 | assert.equal(hash.sha1(obj), hash(obj, {algorithm: 'sha1'}), 'sha1'); 89 | assert.equal(hash.MD5(obj), hash(obj, {algorithm: 'md5'}), 'md5'); 90 | assert.equal(hash.keysMD5(obj), 91 | hash(obj, {algorithm: 'md5', excludeValues: true}), 'keys md5'); 92 | }); 93 | 94 | it('array of nested object values are hashed', function() { 95 | var hash1 = hash({foo: [ {bar: true, bax: 1}, {bar: false, bax: 2} ] }); 96 | var hash2 = hash({foo: [ {bar: true, bax: 1}, {bar: false, bax: 2} ] }); 97 | var hash3 = hash({foo: [ {bar: false, bax: 2} ] }); 98 | assert.equal(hash1, hash2, 'hashes are equal'); 99 | assert.notEqual(hash1, hash3, 'different objects not equal'); 100 | }); 101 | 102 | it("recursive objects don't blow up stack", function() { 103 | var hash1 = {foo: 'bar'}; 104 | hash1.recursive = hash1; 105 | assert.doesNotThrow(function() {hash(hash1);}, /Maximum call stack size exceeded/, 'Should not throw an stack size exceeded exception'); 106 | }); 107 | 108 | it("recursive arrays don't blow up stack", function() { 109 | var hash1 = ['foo', 'bar']; 110 | hash1.push(hash1); 111 | assert.doesNotThrow(function() {hash(hash1);}, /Maximum call stack size exceeded/, 'Should not throw an stack size exceeded exception'); 112 | }); 113 | 114 | it("recursive arrays don't blow up stack with unorderedArrays", function() { 115 | var hash1 = ['foo', 'bar']; 116 | hash1.push(hash1); 117 | assert.doesNotThrow(function() {hash(hash1, {unorderedArrays: true});}, /Maximum call stack size exceeded/, 'Should not throw an stack size exceeded exception'); 118 | }); 119 | 120 | it("recursive handling tracks identity", function() { 121 | var hash1 = {k1: {k: 'v'}, k2: {k: 'k2'}}; 122 | hash1.k1.r1 = hash1.k1; 123 | hash1.k2.r2 = hash1.k2; 124 | var hash2 = {k1: {k: 'v'}, k2: {k: 'k2'}}; 125 | hash2.k1.r1 = hash2.k2; 126 | hash2.k2.r2 = hash2.k1; 127 | assert.notEqual(hash(hash1), hash(hash2), "order of recursive objects should matter"); 128 | }); 129 | 130 | it("object types are hashed", function() { 131 | var hash1 = hash({foo: 'bar'}); 132 | var hash2 = hash(['foo', 'bar']); 133 | assert.notEqual(hash1, hash2, "arrays and objects should not produce identical hashes"); 134 | }); 135 | 136 | it("utf8 strings are hashed correctly", function() { 137 | var hash1 = hash('\u03c3'); // cf 83 in utf8 138 | var hash2 = hash('\u01c3'); // c7 83 in utf8 139 | assert.notEqual(hash1, hash2, "different strings with similar utf8 encodings should produce different hashes"); 140 | }); 141 | 142 | it("strings passed as new String are hashed correctly", function() { 143 | var hash1 = hash(new String('foo')); 144 | assert.equal(hash1, '7cd3edacc4c9dd43908177508c112608a398bb9a'); 145 | var hash2 = hash({foo: new String('bar')}); 146 | assert.equal(hash2, 'a75c05bdca7d704bdfcd761913e5a4e4636e956b'); 147 | }); 148 | 149 | it("various hashes in crypto.getHashes() should be supported", function() { 150 | var hashes = ['sha1', 'md5']; 151 | 152 | if (crypto.getHashes) { 153 | // take all hashes from crypto.getHashes() starting with MD or SHA 154 | hashes = crypto.getHashes().filter(RegExp.prototype.test.bind(/^(md|sha)/i)); 155 | } 156 | 157 | var obj = {randomText: 'bananas'}; 158 | 159 | for (var i = 0; i < hashes.length; i++) { 160 | assert.ok(hash(obj, {algorithm: hashes[i]}), 'Algorithm ' + hashes[i] + ' should be supported'); 161 | } 162 | }); 163 | 164 | it("null and 'Null' string produce different hashes", function() { 165 | var hash1 = hash({foo: null}); 166 | var hash2 = hash({foo: 'Null'}); 167 | assert.notEqual(hash1, hash2, "null and 'Null' should not produce identical hashes"); 168 | }); 169 | 170 | it('Distinguish functions based on their properties', function() { 171 | 172 | var a, b, c, d; 173 | function Foo() {} 174 | a = hash(Foo); 175 | 176 | Foo.foo = 22; 177 | b = hash(Foo); 178 | 179 | Foo.bar = "42"; 180 | c = hash(Foo); 181 | 182 | Foo.foo = "22"; 183 | d = hash(Foo); 184 | 185 | assert.notEqual(a,b, 'adding a property changes the hash'); 186 | assert.notEqual(b,c, 'adding another property changes the hash'); 187 | assert.notEqual(c,d, 'changing a property changes the hash'); 188 | }); 189 | 190 | it('respectFunctionProperties = false', function() { 191 | 192 | var a, b; 193 | function Foo() {} 194 | a = hash(Foo, {respectFunctionProperties: false}); 195 | 196 | Foo.foo = 22; 197 | b = hash(Foo, {respectFunctionProperties: false}); 198 | 199 | assert.equal(a,b, 'function properties are ignored'); 200 | }); 201 | 202 | it('Distinguish functions based on prototype properties', function() { 203 | 204 | var a, b, c, d; 205 | function Foo() {} 206 | a = hash(Foo); 207 | 208 | Foo.prototype.foo = 22; 209 | b = hash(Foo); 210 | 211 | Foo.prototype.bar = "42"; 212 | c = hash(Foo); 213 | 214 | Foo.prototype.foo = "22"; 215 | d = hash(Foo); 216 | 217 | assert.notEqual(a,b, 'adding a property to the prototype changes the hash'); 218 | assert.notEqual(b,c, 'adding another property to the prototype changes the hash'); 219 | assert.notEqual(c,d, 'changing a property in the prototype changes the hash'); 220 | }); 221 | 222 | it('distinguishes async functions based on their properties', function() { 223 | var a, b; 224 | 225 | var Foo; 226 | 227 | try { 228 | Foo = eval('async function Foo() {}; Foo'); 229 | } catch (err) { 230 | if (err.name === 'SyntaxError') { 231 | return this.skip('Not available on Node 6'); 232 | } else { 233 | throw err; 234 | } 235 | } 236 | 237 | a = hash(Foo); 238 | 239 | Foo.foo = 22; 240 | b = hash(Foo); 241 | 242 | assert.notEqual(a,b, 'adding a property changes the hash'); 243 | }); 244 | 245 | it('Distinguish objects based on their type', function() { 246 | 247 | function Foo() {} 248 | function Bar() {} 249 | 250 | var f = new Foo(), b = new Bar(); 251 | 252 | assert.notEqual(hash(Foo), hash(Bar), 'Functions with different names should produce a different Hash.'); 253 | assert.notEqual(hash(f), hash(b), 'Objects with different constructor should have a different Hash.'); 254 | }); 255 | 256 | it('respectType = false', function() { 257 | var opt = { respectType: false }; 258 | 259 | 260 | function Foo() {} 261 | function Bar() {} 262 | 263 | var f = new Foo(), b = new Bar(); 264 | assert.equal(hash(f, opt), hash(b, opt), 'Hashing should disregard the different constructor'); 265 | 266 | 267 | var ha, hb; 268 | function F() {} 269 | ha = hash(F, opt); 270 | 271 | F.prototype.meaningOfLife = 42; 272 | hb = hash(F, opt); 273 | 274 | assert.equal(ha, hb, 'Hashing should disregard changes in the function\'s prototype'); 275 | }); 276 | 277 | it('unorderedArrays = false', function() { 278 | var ha, hb; 279 | ha = hash([1, 2, 3]); 280 | hb = hash([3, 2, 1]); 281 | 282 | assert.notEqual(ha, hb, 'Hashing should respect the order of array entries'); 283 | }); 284 | 285 | it('unorderedArrays = true', function() { 286 | var opt = { unorderedArrays: true }; 287 | 288 | var ha, hb; 289 | ha = hash([1, 2, 3], opt); 290 | hb = hash([3, 2, 1], opt); 291 | 292 | assert.equal(ha, hb, 'Hashing should not respect the order of array entries'); 293 | 294 | ha = hash([{a: 1}, {a: 2}], opt); 295 | hb = hash([{a: 2}, {a: 1}], opt); 296 | 297 | assert.equal(ha, hb, 'Hashing should not respect the order of array entries'); 298 | }); 299 | 300 | it('excludeKeys works', function() { 301 | var ha, hb; 302 | ha = hash({a: 1, b: 4}, { excludeKeys: function(key) { return key === 'b' } }); 303 | hb = hash({a: 1}); 304 | 305 | assert.equal(ha, hb, 'Hashing should ignore key `b`'); 306 | }); 307 | 308 | if (typeof Set !== 'undefined') { 309 | it('unorderedSets = false', function() { 310 | var opt = { unorderedSets: false }; 311 | 312 | var ha, hb; 313 | ha = hash(new Set([1, 2, 3]), opt); 314 | hb = hash(new Set([3, 2, 1]), opt); 315 | 316 | assert.notEqual(ha, hb, 'Hashing should respect the order of Set entries'); 317 | }); 318 | 319 | it('unorderedSets = true', function() { 320 | var ha, hb; 321 | ha = hash(new Set([1, 2, 3])); 322 | hb = hash(new Set([3, 2, 1])); 323 | 324 | assert.equal(ha, hb, 'Hashing should not respect the order of Set entries'); 325 | }); 326 | } 327 | 328 | if (typeof Uint8Array !== 'undefined') { 329 | it('respects typed array\'s types', function () { 330 | assert.notEqual(hash(new Int8Array([1,2,3,4])), hash(new Uint8Array([1,2,3,4])), 'Hashing should respect signed / unsigned Int8Array type'); 331 | assert.notEqual(hash(new Int16Array([1,2,3,4])), hash(new Uint16Array([1,2,3,4])), 'Hashing should respect signed / unsigned Int16Array type'); 332 | assert.notEqual(hash(new Int32Array([1,2,3,4])), hash(new Uint32Array([1,2,3,4])), 'Hashing should respect signed / unsigned Int32Array type'); 333 | }); 334 | } 335 | }); 336 | -------------------------------------------------------------------------------- /test/object-classes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var hash = require('../index'); 5 | var validSha1 = /^[0-9a-f]{40}$/i; 6 | 7 | describe('hash() objects with custom class names', function() { 8 | var builtinToString; 9 | beforeEach(function() { 10 | builtinToString = Object.prototype.toString; 11 | Object.prototype.toString = function() { 12 | if (this && typeof this.__className !== 'undefined') { 13 | return this.__className; 14 | } 15 | 16 | return builtinToString.apply(this, arguments); 17 | }; 18 | }); 19 | 20 | afterEach(function() { 21 | Object.prototype.toString = builtinToString; 22 | }); 23 | 24 | it('should throw when trying to hash an unknown object', function() { 25 | assert.throws(function() { 26 | hash({a:1, __className: '[object Foo]'}); 27 | }, /Unknown object type "foo"/); 28 | 29 | assert.throws(function() { 30 | hash({a:1, __className: 'Foo'}); 31 | }, /Unknown object type/); 32 | }); 33 | 34 | it('should not throw when trying to hash an unknown object with ignoreUnknown', function() { 35 | var opt = {ignoreUnknown: true}; 36 | 37 | assert.ok(validSha1.test(hash({a:1, __className: '[object Foo]'}, opt))); 38 | }); 39 | 40 | it('should not throw when trying to hash a weirdly-named object with ignoreUnknown', function() { 41 | var opt = {ignoreUnknown: true}; 42 | 43 | assert.ok(validSha1.test(hash({a:1, __className: 'Foo'}, opt))); 44 | }); 45 | 46 | it('should not delve further into a number of native types', function() { 47 | var nativeTypes = [ 48 | 'domwindow', 49 | 'process', 'timer', 'pipe', 'tcp', 'udp', 'tty', 'statwatcher', 50 | 'securecontext', 'connection', 'zlib', 'context', 'nodescript', 51 | 'httpparser', 'dataview', 'signal', 'fsevent', 'tlswrap' 52 | ]; 53 | 54 | for (var i = 0; i < nativeTypes.length; i++) { 55 | var obj = { foobar: 1, __className: '[object ' + nativeTypes[i] + ']' }; 56 | var serialized = hash(obj, { algorithm: 'passthrough', encoding: 'utf8' }); 57 | assert.strictEqual(serialized, nativeTypes[i]); 58 | } 59 | }); 60 | 61 | it('should hash xml based on its string representation', function() { 62 | var obj = { 63 | __className: '[object xml]', 64 | toString: function() { return 'Bananä' } 65 | }; 66 | 67 | var serialized = hash(obj, { algorithm: 'passthrough', encoding: 'utf8' }); 68 | assert.strictEqual(serialized, 'xml:Bananä'); 69 | }); 70 | 71 | it('should hash URLs based on its string representation', function() { 72 | var obj = { 73 | __className: '[object url]', 74 | toString: function() { return 'https://example.com/' } 75 | }; 76 | 77 | var serialized = hash(obj, { algorithm: 'passthrough', encoding: 'utf8' }); 78 | assert.strictEqual(serialized, 'url:https://example.com/'); 79 | }); 80 | 81 | it('should not hash blobs without ignoreUnknown', function() { 82 | var obj = { 83 | __className: '[object blob]' 84 | }; 85 | 86 | assert.throws(function() { 87 | hash(obj); 88 | }, /not supported/); 89 | }); 90 | 91 | it('should ignore blobs with ignoreUnknown', function() { 92 | var obj = { 93 | __className: '[object blob]' 94 | }; 95 | 96 | var serialized = hash(obj, { 97 | algorithm: 'passthrough', 98 | encoding: 'utf8', 99 | ignoreUnknown: true 100 | }); 101 | 102 | assert.strictEqual(serialized, '[blob]'); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/old-crypto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var crypto = require('crypto'); 5 | var hash = require('../index'); 6 | var validSha1 = /^[0-9a-f]{40}$/i; 7 | var validBase64 = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/==]{4})$/; 8 | 9 | describe('hash() without crypto.getHashes', function() { 10 | var getHashes_; 11 | beforeEach(function() { 12 | getHashes_ = crypto.getHashes; 13 | delete crypto.getHashes; 14 | }); 15 | 16 | afterEach(function() { 17 | crypto.getHashes = getHashes_; 18 | }); 19 | 20 | it('should work fine for SHA1', function() { 21 | assert.ok(validSha1.test(hash(42)), 'hash some value'); 22 | assert.ok(validSha1.test(hash(NaN)), 'hash some value'); 23 | }); 24 | }); 25 | 26 | describe('hash() without Duplex streams', function() { 27 | var createHash_; 28 | beforeEach(function() { 29 | createHash_ = crypto.createHash; 30 | crypto.createHash = function(algorithm) { 31 | var strm = createHash_(algorithm); 32 | 33 | return { 34 | update: strm.write.bind(strm), 35 | digest: strm.digest.bind(strm) 36 | }; 37 | }; 38 | }); 39 | 40 | afterEach(function() { 41 | crypto.createHash = createHash_; 42 | }); 43 | 44 | it('should work fine for SHA1 without .write()/.read()', function() { 45 | assert.ok(validSha1.test(hash(42)), 'hash some value'); 46 | assert.ok(validSha1.test(hash(NaN)), 'hash some value'); 47 | }); 48 | 49 | it('should work fine for SHA1 without .write()/.read() with base64', function() { 50 | assert.ok(validBase64.test(hash(42, {encoding: 'base64'})), 'hash some value'); 51 | assert.ok(validBase64.test(hash(NaN, {encoding: 'base64'})), 'hash some value'); 52 | }); 53 | 54 | if (typeof Buffer !== 'undefined') { 55 | it('should work fine for SHA1 without .write()/.read() with buffer', function() { 56 | assert.ok(Buffer.isBuffer(hash(42, {encoding: 'buffer'})), 'hash some value'); 57 | assert.ok(Buffer.isBuffer(hash(NaN, {encoding: 'buffer'})), 'hash some value'); 58 | }); 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /test/replacer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var stream = require('stream'); 5 | var hash = require('../index'); 6 | 7 | describe('replacer option', function() { 8 | it('should emit information about an object to a stream', function() { 9 | var strm = new stream.PassThrough(); 10 | 11 | var replacer = function(value) { 12 | try { 13 | return JSON.stringify(value); 14 | } catch (e) { 15 | return value; 16 | } 17 | }; 18 | 19 | var obj = {foo: 'bar'}; 20 | hash.writeToStream(obj, {replacer: replacer}, strm); 21 | var result = strm.read().toString(); 22 | assert.strictEqual(typeof result, 'string'); 23 | assert.notStrictEqual(result.indexOf(JSON.stringify(obj)), -1); 24 | }); 25 | 26 | it('should not reach property values when excludeValues = true', function() { 27 | var strm = new stream.PassThrough(); 28 | 29 | var replacer = function(k) { 30 | assert.notStrictEqual(k, 'bar'); 31 | return k; 32 | }; 33 | 34 | hash.writeToStream({foo: 'bar', replacer: replacer}, {excludeValues: true}, strm); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var hash = require('../index'); 5 | var validSha1 = /^[0-9a-f]{40}$/i; 6 | 7 | describe('hash()ing different types', function() { 8 | it('hashes non-object types', function() { 9 | var func = function(a){ return a + 1; }; 10 | var asyncFunc; 11 | try { 12 | asyncFunc = eval('async function(a) { return a + 1; }'); 13 | } catch (err) { 14 | if (err.name === 'SyntaxError') { 15 | asyncFunc = func; 16 | } else { 17 | throw err; 18 | } 19 | } 20 | assert.ok(validSha1.test(hash('Shazbot!')), 'hash string'); 21 | assert.ok(validSha1.test(hash(42)), 'hash number'); 22 | assert.ok(validSha1.test(hash(NaN)), 'hash bool'); 23 | assert.ok(validSha1.test(hash(true)), 'hash bool'); 24 | assert.ok(validSha1.test(hash(func)), 'hash function'); 25 | assert.ok(validSha1.test(hash(asyncFunc)), 'hash async function'); 26 | }); 27 | 28 | it('hashes special object types', function() { 29 | var dt = new Date(); 30 | dt.setDate(dt.getDate() + 1); 31 | 32 | assert.ok(validSha1.test(hash([1,2,3,4])), 'hash array'); 33 | assert.notEqual(hash([1,2,3,4]), hash([4,3,2,1]), 'different arrays not equal'); 34 | assert.ok(validSha1.test(hash(new Date())), 'hash date'); 35 | assert.notEqual(hash(new Date()), hash(dt), 'different dates not equal'); 36 | assert.ok(validSha1.test(hash(null)), 'hash Null'); 37 | assert.ok(validSha1.test(hash(Number.NaN)), 'hash NaN'); 38 | assert.ok(validSha1.test(hash({ foo: undefined })), 'hash Undefined value'); 39 | assert.ok(validSha1.test(hash(new RegExp())), 'hash regex'); 40 | assert.ok(validSha1.test(hash(new Error())), 'hash error'); 41 | }); 42 | 43 | it('hashes node.js-internal object types', function() { 44 | if (typeof process !== 'undefined') { 45 | assert.ok(validSha1.test(hash(process)), 'hash process'); 46 | } 47 | 48 | var timer = setTimeout(function() {}, 0); 49 | assert.ok(validSha1.test(hash(timer)), 'hash timer'); 50 | }); 51 | 52 | if (typeof Symbol !== 'undefined') { 53 | it('hashes Symbols', function() { 54 | assert.ok(validSha1.test(hash(Symbol('Banana'))), 'hash error'); 55 | }); 56 | } 57 | 58 | if (typeof Buffer !== 'undefined') { 59 | it("Buffers can be hashed", function() { 60 | assert.ok(validSha1.test(hash(new Buffer('Banana'))), 'hashes Buffers'); 61 | }); 62 | } 63 | 64 | if (typeof Uint8Array !== 'undefined') { 65 | it("Typed arrays can be hashed", function() { 66 | 67 | assert.ok(validSha1.test(hash(new Uint8Array([1,2,3,4]))), 'hashes Uint8Array'); 68 | assert.ok(validSha1.test(hash(new Int8Array([1,2,3,4]))), 'hashes Int8Array'); 69 | assert.ok(validSha1.test(hash(new Uint16Array([1,2,3,4]))), 'hashes Uint16Array'); 70 | assert.ok(validSha1.test(hash(new Int16Array([1,2,3,4]))), 'hashes Int16Array'); 71 | assert.ok(validSha1.test(hash(new Uint32Array([1,2,3,4]))), 'hashes Uint32Array'); 72 | assert.ok(validSha1.test(hash(new Int32Array([1,2,3,4]))), 'hashes Int32Array'); 73 | assert.ok(validSha1.test(hash(new Float32Array([1,2,3,4]))), 'hashes Float32Array'); 74 | if (typeof Float64Array !== 'undefined') { 75 | assert.ok(validSha1.test(hash(new Float64Array([1,2,3,4]))), 'hashes Float64Array'); 76 | } 77 | if (typeof Uint8ClampedArray !== 'undefined') { 78 | assert.ok(validSha1.test(hash(new Uint8ClampedArray([1,2,3,4]))), 'hashes Uint8ClampedArray'); 79 | } 80 | assert.ok(validSha1.test(hash(new Uint8Array([1,2,3,4]).buffer)), 'hashes ArrayBuffer'); 81 | }); 82 | } 83 | 84 | if (typeof Map !== 'undefined') { 85 | it("Maps can be hashed", function() { 86 | var map = new Map([['a',1],['b',2]]); 87 | assert.ok(validSha1.test(hash(map)), 'hashes Maps'); 88 | }); 89 | } 90 | 91 | if (typeof WeakMap !== 'undefined') { 92 | it("WeakMaps can’t be hashed", function() { 93 | var map = new WeakMap([[{foo: 'bar'},1]]); 94 | assert.throws(function() { 95 | validSha1.test(hash(map)) 96 | }, 'does not hash WeakMaps'); 97 | }); 98 | } 99 | 100 | if (typeof Set !== 'undefined') { 101 | it("Sets can be hashed", function() { 102 | var set = new Set(['you', 'du', 'tu', 'あなた', '您']); 103 | assert.ok(validSha1.test(hash(set)), 'hashes Sets'); 104 | }); 105 | } 106 | 107 | if (typeof WeakSet !== 'undefined') { 108 | it("WeakSets can’t be hashed", function() { 109 | var obj = {foo: 'bar'}; 110 | var set = new WeakSet([obj]); 111 | assert.throws(function() { 112 | validSha1.test(hash(set)) 113 | }, 'does not hash WeakSets'); 114 | }); 115 | } 116 | 117 | if (typeof BigInt !== 'undefined') { 118 | it("BigInts can be hashed", function() { 119 | assert.ok(function() { 120 | validSha1.test(hash(BigInt(42))) 121 | }, 'hashes BigInts'); 122 | }); 123 | } 124 | 125 | it("Builtin types themselves can be hashed", function() { 126 | var hashcount = {}; 127 | var types = [Object, Date, Number, String, Function, RegExp, 128 | Error, 0, null, NaN]; 129 | if (typeof WeakSet !== 'undefined') {types.push(WeakSet);} 130 | if (typeof Set !== 'undefined') {types.push(Set);} 131 | if (typeof WeakMap !== 'undefined') {types.push(WeakMap);} 132 | if (typeof Map !== 'undefined') {types.push(Map);} 133 | if (typeof Symbol !== 'undefined') {types.push(Symbol);} 134 | if (typeof Uint8Array !== 'undefined') {types.push(Uint8Array);} 135 | if (typeof BigInt !== 'undefined') {types.push(BigInt);} 136 | 137 | // Hash each type 138 | for (var idx in types) { 139 | var h = hash(types[idx]); 140 | assert.ok(validSha1.test(h)); 141 | hashcount[h] = (hashcount[h] || 0) + 1; 142 | } 143 | 144 | // Check for collisions 145 | var no = 0; 146 | for (h in hashcount) { 147 | assert.equal(hashcount[h], 1); 148 | no++; 149 | } 150 | 151 | // Self check; did we really hash all the types? 152 | assert.equal(no, types.length); 153 | }); 154 | 155 | it("Builtin types might result in identical hashes with respectFunctionNames = false", function() { 156 | var hashcount = {}; 157 | var types = [Object, Date, Number, String, Function, RegExp, 158 | Error, 0, null, NaN]; 159 | if (typeof WeakSet !== 'undefined') {types.push(WeakSet);} 160 | if (typeof Set !== 'undefined') {types.push(Set);} 161 | if (typeof WeakMap !== 'undefined') {types.push(WeakMap);} 162 | if (typeof Map !== 'undefined') {types.push(Map);} 163 | if (typeof Symbol !== 'undefined') {types.push(Symbol);} 164 | if (typeof Uint8Array !== 'undefined') {types.push(Uint8Array);} 165 | if (typeof BigInt !== 'undefined') {types.push(BigInt);} 166 | 167 | // Hash each type 168 | for (var idx in types) { 169 | var h = hash(types[idx], { respectFunctionNames: false }); 170 | assert.ok(validSha1.test(h)); 171 | hashcount[h] = (hashcount[h] || 0) + 1; 172 | } 173 | 174 | // Check for collisions 175 | var no = 0; 176 | for (h in hashcount) { 177 | assert.ok(hashcount[h] >= 1); 178 | no += hashcount[h]; 179 | } 180 | 181 | // Self check; did we really hash all the types? 182 | assert.equal(no, types.length); 183 | }); 184 | 185 | it("Functions with identical bodies and different names result in identical hashes with respectFunctionNames = false", function() { 186 | var fn1 = function a() {}; 187 | var fn2 = function b() {}; 188 | var toStringDummy = function() { return '...'; }; 189 | fn1.toString = toStringDummy; 190 | fn2.toString = toStringDummy; 191 | 192 | var h1 = hash(fn1, { respectFunctionNames: false }); 193 | var h2 = hash(fn2, { respectFunctionNames: false }); 194 | assert.strictEqual(h1, h2); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /test/writeToStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var stream = require('stream'); 5 | var hash = require('../index'); 6 | 7 | describe('writeToStream', function() { 8 | it('should emit information about an object to a stream', function() { 9 | var strm = new stream.PassThrough(); 10 | 11 | hash.writeToStream({foo: 'bar'}, strm); 12 | var result = strm.read().toString(); 13 | assert.strictEqual(typeof result, 'string'); 14 | assert.notStrictEqual(result.indexOf('foo'), -1); 15 | assert.notStrictEqual(result.indexOf('bar'), -1); 16 | }); 17 | 18 | it('should leave out keys when excludeValues = true', function() { 19 | var strm = new stream.PassThrough(); 20 | 21 | hash.writeToStream({foo: 'bar'}, {excludeValues: true}, strm); 22 | var result = strm.read().toString(); 23 | assert.strictEqual(typeof result, 'string'); 24 | assert.notStrictEqual(result.indexOf('foo'), -1); 25 | assert. strictEqual(result.indexOf('bar'), -1); 26 | }); 27 | }); 28 | --------------------------------------------------------------------------------