├── .jscsrc ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── blob-reader.min.js └── index.js ├── docs └── README.md ├── gulpfile.js ├── karma.conf.js ├── package.json ├── src └── index.js └── test └── blob-reader.spec.js /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": ["else", "for", "while", "do", "try", "catch"], 3 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch", "function"], 4 | "requireSpacesInFunctionExpression": { 5 | "beforeOpeningCurlyBrace": true 6 | }, 7 | "disallowMultipleVarDecl": true, 8 | "requireSpacesInsideObjectBrackets": "allButNested", 9 | "disallowSpacesInsideArrayBrackets": true, 10 | "disallowSpacesInsideParentheses": true, 11 | "disallowSpaceAfterObjectKeys": true, 12 | "disallowQuotedKeysInObjects": true, 13 | "requireSpaceBeforeBinaryOperators": ["?", "+", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 14 | "disallowSpaceAfterBinaryOperators": ["!"], 15 | "requireSpaceAfterBinaryOperators": ["?", ",", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 16 | "disallowSpaceBeforeBinaryOperators": [","], 17 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 18 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 19 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], 20 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], 21 | "disallowImplicitTypeConversion": ["numeric", "binary", "string"], 22 | "disallowKeywords": ["with", "eval"], 23 | "disallowMultipleLineBreaks": true, 24 | "disallowKeywordsOnNewLine": ["else"], 25 | "requireLineFeedAtFileEnd": true, 26 | "disallowTrailingWhitespace": true, 27 | "excludeFiles": ["node_modules/**", "bower_components/**"], 28 | "validateIndentation": 2 29 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "jquery": true, 4 | "node": true, 5 | "esnext": true, 6 | "bitwise": true, 7 | "camelcase": true, 8 | "curly": true, 9 | "eqeqeq": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "noarg": true, 13 | "newcap": false, 14 | "quotmark": "single", 15 | "unused": true, 16 | "strict": true, 17 | "trailing": true, 18 | "smarttabs": true, 19 | "white": true, 20 | "freeze": true, 21 | "immed": true, 22 | "noempty": true, 23 | "plusplus": true, 24 | "undef": true, 25 | "laxbreak": true, 26 | "maxdepth": 3, 27 | "loopfunc": true, 28 | "maxcomplexity": 9, 29 | "maxlen": 80, 30 | "maxparams": 4 31 | } 32 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | ._* 3 | .DS_Store 4 | .git 5 | .hg 6 | .npmrc 7 | .lock-wscript 8 | .svn 9 | .wafpickle-* 10 | config.gypi 11 | CVS 12 | npm-debug.log 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm install -g gulp 9 | script: gulp test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Minko Gechev 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://travis-ci.org/mgechev/blobreader.svg?branch=master) 2 | 3 | # BlobReader 4 | 5 | Simple interface for reading blobs, sequentially. 3202 bytes minified and 1172 bytes minified + gzipped. 6 | 7 | # Example 8 | 9 | ```javascript 10 | // Blob definition 11 | var uint8 = new Uint8Array([1, 2]); 12 | var uint16 = new Uint16Array([3]); 13 | var uint82 = new Uint8Array([4, 3]); 14 | var uint32 = new Uint32Array([8]); 15 | var blob = new Blob([uint8, uint16, uint82, uint32]); 16 | 17 | // Reading the blob 18 | BlobReader(blob) 19 | .readUint8('uint8', 2) 20 | .readUint16('uint16') 21 | .readUint8('uint82', 2) 22 | .skip() 23 | .readUint32('uint32') 24 | .commit(function (data) { 25 | expect(data.uint8[0]).toBe(1); 26 | expect(data.uint8[1]).toBe(2); 27 | expect(data.uint16).toBe(3); 28 | expect(typeof data.uint82).toBe('number'); 29 | expect(data.uint82).toBe(4); 30 | expect(data.uint32).toBe(8); 31 | }); 32 | ``` 33 | 34 | # Roadmap 35 | 36 | * Add support for synchronous blob reading in workers. 37 | * *Add support for observables/promises.* 38 | 39 | # License 40 | 41 | MIT 42 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blobreader", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/mgechev/blobreader", 5 | "authors": [ 6 | "mgechev " 7 | ], 8 | "description": "Nice interface for reading blobs in the browser", 9 | "main": "src/index.js", 10 | "keywords": [ 11 | "blob", 12 | "html5", 13 | "javascript", 14 | "reader" 15 | ], 16 | "license": "MIT", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /dist/blob-reader.min.js: -------------------------------------------------------------------------------- 1 | !function t(n,r,i){function e(s,u){if(!r[s]){if(!n[s]){var a="function"==typeof require&&require;if(!u&&a)return a(s,!0);if(o)return o(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var h=r[s]={exports:{}};n[s][0].call(h.exports,function(t){var r=n[s][1][t];return e(r?r:t)},h,h.exports,t,n,r,i)}return r[s].exports}for(var o="function"==typeof require&&require,s=0;sthis._blob.size)throw new Error("Limit reached. Trying to read "+(this._position+n.count)+" bytes out of "+this._blob.size+".");n.type||(n.type=r.BINARY_STRING);var e=this._blob.slice(this._position,this._position+n.count);if(n.type===r.BLOB)t.call(this,e);else{var o=new FileReader;this._pendingTask=!0,o.onload=function(n){t.call(this,n.target.result)}.bind(this),o.onerror=function(){throw new Error("Error while reading the blob")},o["readAs"+n.type](e)}}}function e(t,n){for(var r=0;r>8&255})},swap32:function(t){return e(t,function(t){return(255&t)<<24|(65280&t)<<8|t>>8&65280|t>>24&255})}};r.prototype.readUint8=function(t,n,r){return o.call(this,t,n,1,r||this._dataEndianness)},r.prototype.readUint16=function(t,n,r){return o.call(this,t,n,2,r||this._dataEndianness)},r.prototype.readUint32=function(t,n,r){return o.call(this,t,n,4,r||this._dataEndianness)},r.prototype.readBlob=function(t,n){return this._queue.push({count:n,type:r.BLOB,cb:function(n){this._currentResult[t]=n}.bind(this)}),this},r.prototype.skip=function(t){return t=t||1,this._queue.push({count:t,type:r.BLOB,cb:function(){}}),this},r.prototype.commit=function(t){var n={count:0,type:r.BLOB,cb:function(){var n=this._currentResult;this._currentResult={},t(n)}.bind(this)};return this._queue.push(n),this},t.BlobReader=r}("undefined"!=typeof window?window:n.exports)},{}]},{},[1]); -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o this._blob.size) { 79 | throw new Error('Limit reached. Trying to read ' + 80 | (this._position + current.count) + ' bytes out of ' + 81 | this._blob.size + '.'); 82 | } 83 | if (!current.type) { 84 | current.type = BlobReader.BINARY_STRING; 85 | } 86 | var slice = this._blob 87 | .slice(this._position, this._position + current.count); 88 | if (current.type === BlobReader.BLOB) { 89 | done.call(this, slice); 90 | } else { 91 | var reader = new FileReader(); 92 | this._pendingTask = true; 93 | reader.onload = function (e) { 94 | done.call(this, e.target.result); 95 | }.bind(this); 96 | reader.onerror = function () { 97 | throw new Error('Error while reading the blob'); 98 | }; 99 | reader['readAs' + current.type](slice); 100 | } 101 | } 102 | 103 | /** 104 | * Read definite amount of bytes by the blob 105 | * 106 | * @public 107 | * @param {Number} count The count of bytes, which should be read 108 | * @param {Function} cb The callback, which should be 109 | * invoked once the bytes are read 110 | * @return {BlobReader} `this` 111 | */ 112 | BlobReader.prototype.read = function (count, type, cb) { 113 | if (typeof count === 'string') { 114 | cb = type; 115 | count = undefined; 116 | type = count; 117 | } 118 | if (typeof count === 'function') { 119 | cb = count; 120 | count = undefined; 121 | } 122 | if (typeof type === 'function') { 123 | cb = type; 124 | type = undefined; 125 | } 126 | this._queue.push({ 127 | count: count, 128 | cb: cb, 129 | type: type 130 | }); 131 | if (!this._pendingTask) { 132 | invokeNext.call(this); 133 | } 134 | return this; 135 | }; 136 | 137 | /** 138 | * Read defined amount of bytes as text 139 | * 140 | * @public 141 | * @param {Number} count Number of bytes to be read 142 | * @param {Function} cb Callback to be invoked 143 | * @return {BlobReader} The target object 144 | */ 145 | BlobReader.prototype.readText = function (count, cb) { 146 | return this.read(count, BlobReader.TEXT, cb); 147 | }; 148 | 149 | /** 150 | * Read defined amount of bytes as array buffer 151 | * 152 | * @public 153 | * @param {Number} count Number of bytes to be read 154 | * @param {Function} cb Callback to be invoked 155 | * @return {BlobReader} The target object 156 | */ 157 | BlobReader.prototype.readArrayBuffer = function (count, cb) { 158 | return this.read(count, BlobReader.ARRAY_BUFFER, cb); 159 | }; 160 | 161 | /** 162 | * Read defined amount of bytes as binary string 163 | * 164 | * @public 165 | * @param {Number} count Number of bytes to be read 166 | * @param {Function} cb Callback to be invoked 167 | * @return {BlobReader} The target object 168 | */ 169 | BlobReader.prototype.readBinaryString = function (count, cb) { 170 | return this.read(count, BlobReader.BINARY_STRING, cb); 171 | }; 172 | 173 | /** 174 | * Read defined amount of bytes as data URL 175 | * 176 | * @public 177 | * @param {Number} count Number of bytes to be read 178 | * @param {Function} cb Callback to be invoked 179 | * @return {BlobReader} The target object 180 | */ 181 | BlobReader.prototype.readDataURL = function (count, cb) { 182 | return this.read(count, BlobReader.DATA_URL, cb); 183 | }; 184 | 185 | function swap(arr, cb) { 186 | for (var i = 0; i < arr.length; i += 1) { 187 | arr[i] = cb(arr[i]); 188 | } 189 | return arr; 190 | } 191 | 192 | var byteFormatter = { 193 | swap8: function (arr) { 194 | return swap(arr, function (val) { 195 | return val; 196 | }); 197 | }, 198 | swap16: function (arr) { 199 | return swap(arr, function (val) { 200 | return ((val & 0xFF) << 8) | 201 | ((val >> 8) & 0xFF); 202 | }); 203 | }, 204 | swap32: function (arr) { 205 | return swap(arr, function (val) { 206 | return ((val & 0xFF) << 24) | 207 | ((val & 0xFF00) << 8) | 208 | ((val >> 8) & 0xFF00) | 209 | ((val >> 24) & 0xFF); 210 | }); 211 | } 212 | }; 213 | 214 | /* jshint validthis: true */ 215 | function uintReader(name, count, octets, endianness) { 216 | count = (count || 1) * octets; 217 | var callback = function (data) { 218 | var bitsNum = 8 * octets; 219 | var type = window['Uint' + bitsNum + 'Array']; 220 | data = new type(data); 221 | if (this._currentEndianness !== endianness) { 222 | data = byteFormatter['swap' + bitsNum](data); 223 | } 224 | if (count === octets) { 225 | data = data[0]; 226 | } 227 | this._currentResult[name] = data; 228 | }.bind(this); 229 | return this.readArrayBuffer(count, callback); 230 | } 231 | 232 | /** 233 | * Read defined amount of bytes as uint8 array 234 | * 235 | * @public 236 | * @param {String} name Property name 237 | * @param {Number} count Number of 8 bit numbers to be read 238 | * @param {BlobReader.ENDIANNESS} endianness Endianness of the 239 | * bytes which should be read. If differ from the system's endianness 240 | * the values will be converted 241 | * @return {BlobReader} The target object 242 | */ 243 | BlobReader.prototype.readUint8 = function (name, count, endianness) { 244 | return uintReader.call(this, name, count, 1, 245 | endianness || this._dataEndianness); 246 | }; 247 | 248 | /** 249 | * Read defined amount of bytes as uint16 array 250 | * 251 | * @public 252 | * @param {String} name Property name 253 | * @param {Number} count Number of 16 bit numbers to be read 254 | * @param {BlobReader.ENDIANNESS} endianness Endianness of the 255 | * bytes which should be read. If differ from the system's endianness 256 | * the values will be converted 257 | * @return {BlobReader} The target object 258 | */ 259 | BlobReader.prototype.readUint16 = function (name, count, endianness) { 260 | return uintReader.call(this, name, count, 2, 261 | endianness || this._dataEndianness); 262 | }; 263 | 264 | /** 265 | * Read defined amount of bytes as uint32 array 266 | * 267 | * @public 268 | * @param {String} name Property name 269 | * @param {Number} count Number of 32 bit numbers to be read 270 | * @param {BlobReader.ENDIANNESS} endianness Endianness of the 271 | * bytes which should be read. If differ from the system's endianness 272 | * the values will be converted 273 | * @return {BlobReader} The target object 274 | */ 275 | BlobReader.prototype.readUint32 = function (name, count, endianness) { 276 | return uintReader.call(this, name, count, 4, 277 | endianness || this._dataEndianness); 278 | }; 279 | 280 | /** 281 | * Read a blob and push it to the result object 282 | * 283 | * @public 284 | * @param {String} name Property name 285 | * @param {Number} count Number of bytes to be sliced from the Blob 286 | * @return {BlobReader} The target object 287 | */ 288 | BlobReader.prototype.readBlob = function (name, count) { 289 | this._queue.push({ 290 | count: count, 291 | type: BlobReader.BLOB, 292 | cb: function (result) { 293 | this._currentResult[name] = result; 294 | }.bind(this) 295 | }); 296 | return this; 297 | }; 298 | 299 | /** 300 | * Skips defined amount of bytes, usually used for padding 301 | * 302 | * @public 303 | * @param {Number} count Number of bytes to be skipped 304 | * @return {BlobReader} The target object 305 | */ 306 | BlobReader.prototype.skip = function (count) { 307 | count = count || 1; 308 | this._queue.push({ 309 | count: count, 310 | type: BlobReader.BLOB, 311 | cb: function () {} 312 | }); 313 | return this; 314 | }; 315 | 316 | /** 317 | * Gets the result object 318 | * 319 | * @public 320 | * @return {Object} The object resulted from the calls of readUint 321 | */ 322 | BlobReader.prototype.commit = function (cb) { 323 | var task = { 324 | count: 0, 325 | type: BlobReader.BLOB, 326 | cb: function () { 327 | var res = this._currentResult; 328 | this._currentResult = {}; 329 | cb(res); 330 | }.bind(this) 331 | }; 332 | this._queue.push(task); 333 | return this; 334 | }; 335 | 336 | global.BlobReader = BlobReader; 337 | 338 | }(typeof window !== 'undefined' ? window : module.exports)); 339 | 340 | 341 | },{}]},{},[1]); 342 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | 4 | 5 | 6 | 7 | * * * 8 | 9 | ## Class: BlobReader 10 | Constructor function for the blob reader. 11 | 12 | ### BlobReader.read(count, cb) 13 | 14 | Read definite amount of bytes by the blob 15 | 16 | **Parameters** 17 | 18 | **count**: `Number`, The count of bytes, which should be read 19 | 20 | **cb**: `function`, The callback, which should be 21 | invoked once the bytes are read 22 | 23 | **Returns**: `BlobReader`, `this` 24 | 25 | ### BlobReader.readText(count, cb) 26 | 27 | Read defined amount of bytes as text 28 | 29 | **Parameters** 30 | 31 | **count**: `Number`, Number of bytes to be read 32 | 33 | **cb**: `function`, Callback to be invoked 34 | 35 | **Returns**: `BlobReader`, The target object 36 | 37 | ### BlobReader.readArrayBuffer(count, cb) 38 | 39 | Read defined amount of bytes as array buffer 40 | 41 | **Parameters** 42 | 43 | **count**: `Number`, Number of bytes to be read 44 | 45 | **cb**: `function`, Callback to be invoked 46 | 47 | **Returns**: `BlobReader`, The target object 48 | 49 | ### BlobReader.readBinaryString(count, cb) 50 | 51 | Read defined amount of bytes as binary string 52 | 53 | **Parameters** 54 | 55 | **count**: `Number`, Number of bytes to be read 56 | 57 | **cb**: `function`, Callback to be invoked 58 | 59 | **Returns**: `BlobReader`, The target object 60 | 61 | ### BlobReader.readDataURL(count, cb) 62 | 63 | Read defined amount of bytes as data URL 64 | 65 | **Parameters** 66 | 67 | **count**: `Number`, Number of bytes to be read 68 | 69 | **cb**: `function`, Callback to be invoked 70 | 71 | **Returns**: `BlobReader`, The target object 72 | 73 | ### BlobReader.readUint8(name, count, endianness) 74 | 75 | Read defined amount of bytes as uint8 array 76 | 77 | **Parameters** 78 | 79 | **name**: `String`, Property name 80 | 81 | **count**: `Number`, Number of 8 bit numbers to be read 82 | 83 | **endianness**: `BlobReader.ENDIANNESS`, Endianness of the 84 | bytes which should be read. If differ from the system's endianness 85 | the values will be converted 86 | 87 | **Returns**: `BlobReader`, The target object 88 | 89 | ### BlobReader.readUint16(name, count, endianness) 90 | 91 | Read defined amount of bytes as uint16 array 92 | 93 | **Parameters** 94 | 95 | **name**: `String`, Property name 96 | 97 | **count**: `Number`, Number of 16 bit numbers to be read 98 | 99 | **endianness**: `BlobReader.ENDIANNESS`, Endianness of the 100 | bytes which should be read. If differ from the system's endianness 101 | the values will be converted 102 | 103 | **Returns**: `BlobReader`, The target object 104 | 105 | ### BlobReader.readUint32(name, count, endianness) 106 | 107 | Read defined amount of bytes as uint32 array 108 | 109 | **Parameters** 110 | 111 | **name**: `String`, Property name 112 | 113 | **count**: `Number`, Number of 32 bit numbers to be read 114 | 115 | **endianness**: `BlobReader.ENDIANNESS`, Endianness of the 116 | bytes which should be read. If differ from the system's endianness 117 | the values will be converted 118 | 119 | **Returns**: `BlobReader`, The target object 120 | 121 | ### BlobReader.readBlob(name, count) 122 | 123 | Read a blob and push it to the result object 124 | 125 | **Parameters** 126 | 127 | **name**: `String`, Property name 128 | 129 | **count**: `Number`, Number of bytes to be sliced from the Blob 130 | 131 | **Returns**: `BlobReader`, The target object 132 | 133 | ### BlobReader.skip(count) 134 | 135 | Skips defined amount of bytes, usually used for padding 136 | 137 | **Parameters** 138 | 139 | **count**: `Number`, Number of bytes to be skipped 140 | 141 | **Returns**: `BlobReader`, The target object 142 | 143 | ### BlobReader.commit() 144 | 145 | Gets the result object 146 | 147 | **Returns**: `Object`, The object resulted from the calls of readUint 148 | 149 | 150 | 151 | * * * 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var uglify = require('gulp-uglify'); 3 | var rename = require('gulp-rename'); 4 | var karma = require('karma').server; 5 | var browserify = require('browserify'); 6 | var source = require('vinyl-source-stream'); 7 | var buffer = require('vinyl-buffer'); 8 | 9 | gulp.task('build', function () { 10 | 'use strict'; 11 | return browserify(['./src/index.js']) 12 | .bundle() 13 | .pipe(source('index.js')) 14 | .pipe(buffer()) 15 | .pipe(rename('index.js')) 16 | .pipe(gulp.dest('./dist')); 17 | }); 18 | 19 | gulp.task('minify', ['build'], function () { 20 | 'use strict'; 21 | return gulp.src('./dist/index.js') 22 | .pipe(uglify()) 23 | .pipe(rename('./blob-reader.min.js')) 24 | .pipe(gulp.dest('./dist')); 25 | }); 26 | 27 | gulp.task('test', function () { 28 | 'use strict'; 29 | karma.start({ 30 | configFile: __dirname + '/karma.conf.js', 31 | singleRun: true 32 | }); 33 | }); 34 | 35 | gulp.task('autoTest', function () { 36 | 'use strict'; 37 | karma.start({ 38 | configFile: __dirname + '/karma.conf.js', 39 | singleRun: false 40 | }); 41 | }); 42 | 43 | gulp.task('default', ['minify', 'test']); 44 | 45 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Jan 22 2015 13:57:12 GMT+0200 (EET) 3 | 4 | module.exports = function (config) { 5 | 'use strict'; 6 | 7 | config.set({ 8 | 9 | // base path that will be used to resolve all patterns (eg. files, exclude) 10 | basePath: '', 11 | 12 | // frameworks to use 13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 14 | frameworks: ['jasmine'], 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | './dist/index.js', 19 | './test/*.spec.js' 20 | ], 21 | 22 | // list of files to exclude 23 | exclude: [ 24 | ], 25 | 26 | // preprocess matching files before serving them to the browser 27 | // available preprocessors: 28 | // https://npmjs.org/browse/keyword/karma-preprocessor 29 | preprocessors: { 30 | }, 31 | 32 | // test results reporter to use 33 | // possible values: 'dots', 'progress' 34 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 35 | reporters: ['progress'], 36 | 37 | // web server port 38 | port: 9876, 39 | 40 | // enable / disable colors in the output (reporters and logs) 41 | colors: true, 42 | 43 | // level of logging 44 | // possible values: 45 | // config.LOG_DISABLE || config.LOG_ERROR || 46 | // config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 47 | logLevel: config.LOG_INFO, 48 | 49 | // enable / disable watching file and 50 | // executing tests whenever any file changes 51 | autoWatch: true, 52 | 53 | // start these browsers 54 | // available browser launchers: 55 | // https://npmjs.org/browse/keyword/karma-launcher 56 | browsers: ['Firefox'], 57 | 58 | // Continuous Integration mode 59 | // if true, Karma captures browsers, runs the tests and exits 60 | singleRun: false 61 | }); 62 | }; 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blob-reader", 3 | "version": "0.0.1", 4 | "description": "Simple interface for reading Blobs", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/mgechev/blob-reader.git" 12 | }, 13 | "keywords": [ 14 | "blob", 15 | "javascript", 16 | "html5", 17 | "reader" 18 | ], 19 | "author": "@mgechev", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/mgechev/blob-reader/issues" 23 | }, 24 | "homepage": "https://github.com/mgechev/blob-reader", 25 | "devDependencies": { 26 | "gulp": "^3.8.10", 27 | "gulp-rename": "^1.2.2", 28 | "gulp-uglify": "^1.1.0", 29 | "jasmine-core": "^2.1.3", 30 | "karma": "^0.12.31", 31 | "karma-firefox-launcher": "^0.1.4", 32 | "karma-jasmine": "^0.3.5", 33 | "vinyl-buffer": "^1.0.0", 34 | "vinyl-source-stream": "^1.1.0" 35 | }, 36 | "dependencies": { 37 | "browserify": "^12.0.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /*jshint bitwise: false*/ 2 | 3 | ;(function (global) { 4 | 5 | function getEndianness() { 6 | var a = new ArrayBuffer(4); 7 | var b = new Uint8Array(a); 8 | var c = new Uint32Array(a); 9 | b[0] = 0xa1; 10 | b[1] = 0xb2; 11 | b[2] = 0xc3; 12 | b[3] = 0xd4; 13 | if (c[0] === 0xd4c3b2a1) { 14 | return BlobReader.ENDIANNESS.LITTLE_ENDIAN; 15 | } 16 | if (c[0] === 0xa1b2c3d4) { 17 | return BlobReader.ENDIANNESS.BIG_ENDIAN; 18 | } else { 19 | throw new Error('Unrecognized endianness'); 20 | } 21 | } 22 | /** 23 | * Constructor function for the blob reader. 24 | * 25 | * @public 26 | * @constructor 27 | * @param {Blob} blob The blob object, which should be read 28 | * @param {BlobReader.ENDIANNESS} dataEndianness Endianness of the 29 | * expected data 30 | * @param {BlobReader.ENDIANNESS} endianness System endianness 31 | */ 32 | function BlobReader(blob, dataEndianness, endianness) { 33 | if (!(this instanceof BlobReader)) { 34 | return new BlobReader(blob, dataEndianness, endianness); 35 | } 36 | this._blob = blob; 37 | this._position = 0; 38 | this._queue = []; 39 | this._currentEndianness = endianness || getEndianness(); 40 | this._dataEndianness = dataEndianness || this._currentEndianness; 41 | this._pendingTask = false; 42 | this._currentResult = {}; 43 | } 44 | 45 | BlobReader.ARRAY_BUFFER = 'ArrayBuffer'; 46 | BlobReader.BINARY_STRING = 'BinaryString'; 47 | BlobReader.TEXT = 'Text'; 48 | BlobReader.DATA_URL = 'DataURL'; 49 | BlobReader.BLOB = 'Blob'; 50 | 51 | BlobReader.ENDIANNESS = { 52 | BIG_ENDIAN: 'BIG_ENDIAN', 53 | LITTLE_ENDIAN: 'LITTLE_ENDIAN' 54 | }; 55 | 56 | BlobReader.prototype.setDataEndianness = function (endianness) { 57 | this._dataEndianness = endianness; 58 | }; 59 | 60 | /* jshint validthis: true */ 61 | function invokeNext() { 62 | 63 | function done(data) { 64 | current.cb(data); 65 | this._pendingTask = false; 66 | this._position += current.count; 67 | invokeNext.call(this); 68 | } 69 | 70 | var current = this._queue.shift(); 71 | if (!current) { 72 | return; 73 | } 74 | if (current.count === undefined) { 75 | current.count = this._blob.size - this._position; 76 | } 77 | if (this._position + current.count > this._blob.size) { 78 | throw new Error('Limit reached. Trying to read ' + 79 | (this._position + current.count) + ' bytes out of ' + 80 | this._blob.size + '.'); 81 | } 82 | if (!current.type) { 83 | current.type = BlobReader.BINARY_STRING; 84 | } 85 | var slice = this._blob 86 | .slice(this._position, this._position + current.count); 87 | if (current.type === BlobReader.BLOB) { 88 | done.call(this, slice); 89 | } else { 90 | var reader = new FileReader(); 91 | this._pendingTask = true; 92 | reader.onload = function (e) { 93 | done.call(this, e.target.result); 94 | }.bind(this); 95 | reader.onerror = function () { 96 | throw new Error('Error while reading the blob'); 97 | }; 98 | reader['readAs' + current.type](slice); 99 | } 100 | } 101 | 102 | /** 103 | * Read definite amount of bytes by the blob 104 | * 105 | * @public 106 | * @param {Number} count The count of bytes, which should be read 107 | * @param {Function} cb The callback, which should be 108 | * invoked once the bytes are read 109 | * @return {BlobReader} `this` 110 | */ 111 | BlobReader.prototype.read = function (count, type, cb) { 112 | if (typeof count === 'string') { 113 | cb = type; 114 | count = undefined; 115 | type = count; 116 | } 117 | if (typeof count === 'function') { 118 | cb = count; 119 | count = undefined; 120 | } 121 | if (typeof type === 'function') { 122 | cb = type; 123 | type = undefined; 124 | } 125 | this._queue.push({ 126 | count: count, 127 | cb: cb, 128 | type: type 129 | }); 130 | if (!this._pendingTask) { 131 | invokeNext.call(this); 132 | } 133 | return this; 134 | }; 135 | 136 | /** 137 | * Read defined amount of bytes as text 138 | * 139 | * @public 140 | * @param {Number} count Number of bytes to be read 141 | * @param {Function} cb Callback to be invoked 142 | * @return {BlobReader} The target object 143 | */ 144 | BlobReader.prototype.readText = function (count, cb) { 145 | return this.read(count, BlobReader.TEXT, cb); 146 | }; 147 | 148 | /** 149 | * Read defined amount of bytes as array buffer 150 | * 151 | * @public 152 | * @param {Number} count Number of bytes to be read 153 | * @param {Function} cb Callback to be invoked 154 | * @return {BlobReader} The target object 155 | */ 156 | BlobReader.prototype.readArrayBuffer = function (count, cb) { 157 | return this.read(count, BlobReader.ARRAY_BUFFER, cb); 158 | }; 159 | 160 | /** 161 | * Read defined amount of bytes as binary string 162 | * 163 | * @public 164 | * @param {Number} count Number of bytes to be read 165 | * @param {Function} cb Callback to be invoked 166 | * @return {BlobReader} The target object 167 | */ 168 | BlobReader.prototype.readBinaryString = function (count, cb) { 169 | return this.read(count, BlobReader.BINARY_STRING, cb); 170 | }; 171 | 172 | /** 173 | * Read defined amount of bytes as data URL 174 | * 175 | * @public 176 | * @param {Number} count Number of bytes to be read 177 | * @param {Function} cb Callback to be invoked 178 | * @return {BlobReader} The target object 179 | */ 180 | BlobReader.prototype.readDataURL = function (count, cb) { 181 | return this.read(count, BlobReader.DATA_URL, cb); 182 | }; 183 | 184 | function swap(arr, cb) { 185 | for (var i = 0; i < arr.length; i += 1) { 186 | arr[i] = cb(arr[i]); 187 | } 188 | return arr; 189 | } 190 | 191 | var byteFormatter = { 192 | swap8: function (arr) { 193 | return swap(arr, function (val) { 194 | return val; 195 | }); 196 | }, 197 | swap16: function (arr) { 198 | return swap(arr, function (val) { 199 | return ((val & 0xFF) << 8) | 200 | ((val >> 8) & 0xFF); 201 | }); 202 | }, 203 | swap32: function (arr) { 204 | return swap(arr, function (val) { 205 | return ((val & 0xFF) << 24) | 206 | ((val & 0xFF00) << 8) | 207 | ((val >> 8) & 0xFF00) | 208 | ((val >> 24) & 0xFF); 209 | }); 210 | } 211 | }; 212 | 213 | /* jshint validthis: true */ 214 | function uintReader(name, count, octets, endianness) { 215 | count = (count || 1) * octets; 216 | var callback = function (data) { 217 | var bitsNum = 8 * octets; 218 | var type = window['Uint' + bitsNum + 'Array']; 219 | data = new type(data); 220 | if (this._currentEndianness !== endianness) { 221 | data = byteFormatter['swap' + bitsNum](data); 222 | } 223 | if (count === octets) { 224 | data = data[0]; 225 | } 226 | this._currentResult[name] = data; 227 | }.bind(this); 228 | return this.readArrayBuffer(count, callback); 229 | } 230 | 231 | /** 232 | * Read defined amount of bytes as uint8 array 233 | * 234 | * @public 235 | * @param {String} name Property name 236 | * @param {Number} count Number of 8 bit numbers to be read 237 | * @param {BlobReader.ENDIANNESS} endianness Endianness of the 238 | * bytes which should be read. If differ from the system's endianness 239 | * the values will be converted 240 | * @return {BlobReader} The target object 241 | */ 242 | BlobReader.prototype.readUint8 = function (name, count, endianness) { 243 | return uintReader.call(this, name, count, 1, 244 | endianness || this._dataEndianness); 245 | }; 246 | 247 | /** 248 | * Read defined amount of bytes as uint16 array 249 | * 250 | * @public 251 | * @param {String} name Property name 252 | * @param {Number} count Number of 16 bit numbers to be read 253 | * @param {BlobReader.ENDIANNESS} endianness Endianness of the 254 | * bytes which should be read. If differ from the system's endianness 255 | * the values will be converted 256 | * @return {BlobReader} The target object 257 | */ 258 | BlobReader.prototype.readUint16 = function (name, count, endianness) { 259 | return uintReader.call(this, name, count, 2, 260 | endianness || this._dataEndianness); 261 | }; 262 | 263 | /** 264 | * Read defined amount of bytes as uint32 array 265 | * 266 | * @public 267 | * @param {String} name Property name 268 | * @param {Number} count Number of 32 bit numbers to be read 269 | * @param {BlobReader.ENDIANNESS} endianness Endianness of the 270 | * bytes which should be read. If differ from the system's endianness 271 | * the values will be converted 272 | * @return {BlobReader} The target object 273 | */ 274 | BlobReader.prototype.readUint32 = function (name, count, endianness) { 275 | return uintReader.call(this, name, count, 4, 276 | endianness || this._dataEndianness); 277 | }; 278 | 279 | /** 280 | * Read a blob and push it to the result object 281 | * 282 | * @public 283 | * @param {String} name Property name 284 | * @param {Number} count Number of bytes to be sliced from the Blob 285 | * @return {BlobReader} The target object 286 | */ 287 | BlobReader.prototype.readBlob = function (name, count) { 288 | this._queue.push({ 289 | count: count, 290 | type: BlobReader.BLOB, 291 | cb: function (result) { 292 | this._currentResult[name] = result; 293 | }.bind(this) 294 | }); 295 | return this; 296 | }; 297 | 298 | /** 299 | * Skips defined amount of bytes, usually used for padding 300 | * 301 | * @public 302 | * @param {Number} count Number of bytes to be skipped 303 | * @return {BlobReader} The target object 304 | */ 305 | BlobReader.prototype.skip = function (count) { 306 | count = count || 1; 307 | this._queue.push({ 308 | count: count, 309 | type: BlobReader.BLOB, 310 | cb: function () {} 311 | }); 312 | return this; 313 | }; 314 | 315 | /** 316 | * Gets the result object 317 | * 318 | * @public 319 | * @return {Object} The object resulted from the calls of readUint 320 | */ 321 | BlobReader.prototype.commit = function (cb) { 322 | var task = { 323 | count: 0, 324 | type: BlobReader.BLOB, 325 | cb: function () { 326 | var res = this._currentResult; 327 | this._currentResult = {}; 328 | cb(res); 329 | }.bind(this) 330 | }; 331 | this._queue.push(task); 332 | return this; 333 | }; 334 | 335 | global.BlobReader = BlobReader; 336 | 337 | }(typeof window !== 'undefined' ? window : module.exports)); 338 | 339 | -------------------------------------------------------------------------------- /test/blob-reader.spec.js: -------------------------------------------------------------------------------- 1 | /* global BlobReader, it, expect, describe */ 2 | 3 | describe('BlobReader', function () { 4 | 'use strict'; 5 | 6 | it('should export global function BlobReader', function () { 7 | expect(typeof BlobReader).toBe('function'); 8 | }); 9 | 10 | it('should define the specified public API', function () { 11 | var methods = [ 12 | 'read', 13 | 'readText', 14 | 'readDataURL', 15 | 'readArrayBuffer', 16 | 'readBinaryString', 17 | 'readBlob', 18 | 'readUint8', 19 | 'readUint16', 20 | 'readUint32' 21 | ]; 22 | methods.forEach(function (method) { 23 | expect(typeof BlobReader.prototype[method]).toBe('function'); 24 | }); 25 | }); 26 | 27 | it('should read the whole blob', function (done) { 28 | var str = 'test blob'; 29 | // new Blob doesn't work in PhantomJS 30 | // https://github.com/ariya/phantomjs/issues/11013 31 | var blob = new Blob([str]); 32 | BlobReader(blob) 33 | .read(BlobReader.TEXT, function (text) { 34 | expect(text).toBe(str); 35 | done(); 36 | }); 37 | }); 38 | 39 | it('should read slice of the blob if size specified', function (done) { 40 | var str = 'test blob'; 41 | var blob = new Blob([str]); 42 | BlobReader(blob) 43 | .read(3, BlobReader.TEXT, function (text) { 44 | expect(text).toBe(str.substring(0, 3)); 45 | done(); 46 | }); 47 | }); 48 | 49 | it('should read data as dataURL', function (done) { 50 | BlobReader(new Blob(['foobar'])) 51 | .readDataURL(function (data) { 52 | expect(data.split(',').pop()).toBe(btoa('foobar')); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('should support chaining', function (done) { 58 | var str = 'test blob'; 59 | var blob = new Blob([str]); 60 | BlobReader(blob) 61 | .read(3, BlobReader.TEXT, function (text) { 62 | expect(text).toBe(str.substring(0, 3)); 63 | }) 64 | .read(2, BlobReader.TEXT, function (text) { 65 | expect(text).toBe(str.substring(3, 5)); 66 | done(); 67 | }); 68 | }); 69 | 70 | it('should be able to read parts of the blob as text', function (done) { 71 | var str = 'test blob'; 72 | var blob = new Blob([str]); 73 | BlobReader(blob) 74 | .readText(2, function (text) { 75 | expect(text).toBe('te'); 76 | }) 77 | .readText(function (text) { 78 | expect(text).toBe('st blob'); 79 | done(); 80 | }); 81 | }); 82 | 83 | it('should be able to read parts of blob as array', function (done) { 84 | var uint = new Uint16Array([1, 2, 3, 4]); 85 | var blob = new Blob([uint]); 86 | BlobReader(blob) 87 | .readArrayBuffer(2, function (arr) { 88 | expect(new Uint16Array(arr)[0]).toBe(1); 89 | }) 90 | .readArrayBuffer(function (arr) { 91 | arr = new Uint16Array(arr); 92 | for (var i = 0; i < arr.length; i += 1) { 93 | expect(arr[i]).toBe(uint[i + 1]); 94 | } 95 | done(); 96 | }); 97 | }); 98 | 99 | it('should be able to read a chain of words', function (done) { 100 | var uint8 = new Uint8Array([1, 2]); 101 | var uint16 = new Uint16Array([3]); 102 | var uint82 = new Uint8Array([4, 3]); 103 | var uint32 = new Uint32Array([8]); 104 | var blob = new Blob([uint8, uint16, uint82, uint32]); 105 | BlobReader(blob) 106 | .readUint8('uint8', 2) 107 | .readUint16('uint16') 108 | .readUint8('uint82') 109 | .skip() 110 | .readUint32('uint32') 111 | .commit(function (data) { 112 | expect(data.uint8[0]).toBe(1); 113 | expect(data.uint8[1]).toBe(2); 114 | expect(data.uint16).toBe(3); 115 | expect(typeof data.uint82).toBe('number'); 116 | expect(data.uint82).toBe(4); 117 | expect(data.uint32).toBe(8); 118 | done(); 119 | }); 120 | }); 121 | 122 | it('should work with different endianness', function (done) { 123 | var buffer = new ArrayBuffer(2); 124 | var uint16 = new Uint16Array(buffer); 125 | var uint8 = new Uint8Array(buffer); 126 | uint8[0] = 1; 127 | uint8[1] = 2; 128 | var blob = new Blob([uint16]); 129 | BlobReader(blob, BlobReader.ENDIANNESS.BIG_ENDIAN) 130 | .readUint16('data') 131 | .commit(function (data) { 132 | expect(data.data).toBe(258); 133 | done(); 134 | }); 135 | }); 136 | 137 | it('should work with different endianness for specific word', 138 | function (done) { 139 | var buffer = new ArrayBuffer(2); 140 | var uint16 = new Uint16Array(buffer); 141 | var uint162 = new Uint16Array(buffer); 142 | var uint8 = new Uint8Array(buffer); 143 | uint8[0] = 1; 144 | uint8[1] = 2; 145 | var blob = new Blob([uint16, uint162]); 146 | BlobReader(blob) 147 | .readUint16('u16') 148 | .readUint16('u162', 1, BlobReader.ENDIANNESS.BIG_ENDIAN) 149 | .commit(function (data) { 150 | expect(data.u16).toBe(513); 151 | expect(data.u162).toBe(258); 152 | done(); 153 | }); 154 | }); 155 | 156 | it('should allow multiple commits', function (done) { 157 | var uint8 = new Uint8Array([1, 2]); 158 | var uint16 = new Uint16Array([3]); 159 | var blob = new Blob([uint8, uint16]); 160 | BlobReader(blob) 161 | .readUint8('u1') 162 | .readUint8('u2') 163 | .commit(function (data) { 164 | expect(data.u1).toBe(1); 165 | expect(data.u2).toBe(2); 166 | }) 167 | .readUint16('u3') 168 | .commit(function (data) { 169 | expect(data.u3).toBe(3); 170 | done(); 171 | }); 172 | }); 173 | 174 | it('should allow nested commits', function (done) { 175 | var uint8 = new Uint8Array([1, 2]); 176 | var blob = new Blob([uint8, 'test']); 177 | BlobReader(blob) 178 | .readUint8('uint8') 179 | .skip() 180 | .readBlob('blob') 181 | .commit(function (data) { 182 | expect(data.uint8).toBe(1); 183 | BlobReader(data.blob) 184 | .readText(function (text) { 185 | expect(text).toBe('test'); 186 | done(); 187 | }); 188 | }); 189 | }); 190 | 191 | }); 192 | --------------------------------------------------------------------------------