├── LICENSE ├── README.md └── azure-blob-upload.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Stephen Brannan 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 | angular-azure-blob-upload 2 | ========================= 3 | 4 | AngularJS service for uploading to azure blob storage. 5 | 6 | Provides for the ability to upload an HTML5 File to Azure's Blob Storage. The file is uploaded in chunks to avoid memory issues as a BlockBlob. The upload uses a Shared Access Signature (SAS) to secure the file upload. 7 | 8 | Required dependencies 9 | ----------------------- 10 | * [AngularJS] (http://www.angularjs.com) 11 | 12 | Add the Azure Blob Upload to your index.html file 13 | ```HTML 14 | 15 | ``` 16 | 17 | After downloading the `azure-blob-upload.js` to your AngularJS project then 18 | 19 | Add `'azureBlobUpload'` to your main angular.module like so 20 | ```javascript 21 | angular.module('myapp', ['myApp.controllers', 'myApp.services', 'azureBlobUpload']); 22 | ```` 23 | 24 | How to use 25 | ------------- 26 | Add the azureBlob service as a dependency to your controller like so: 27 | ```javascript 28 | angular.module('myapp') 29 | .controller('MainCtrl', ['$scope', 'azureBlob', function ($scope, azureBlob) { 30 | ... 31 | }]) 32 | ``` 33 | 34 | This will expose the following method 35 | 36 | * azureBlob.upload(config) 37 | 38 | The config object has the following properties 39 | 40 | ```javascript 41 | { 42 | baseUrl: // baseUrl for blob file uri (i.e. http://.blob.core.windows.net//), 43 | sasToken: // Shared access signature querystring key/value prefixed with ?, 44 | file: // File object using the HTML5 File API, 45 | progress: // progress callback function, 46 | complete: // complete callback function, 47 | error: // error callback function, 48 | blockSize: // Use this to override the DefaultBlockSize, 49 | } 50 | ``` 51 | 52 | 53 | CORS 54 | ------------- 55 | 56 | Cross Origin Resource Sharing (CORS) must be enabled on the azure blob storage account. The following articles can assist with this... 57 | 58 | [Windows Azure Storage Introducing CORS](http://blogs.msdn.com/b/windowsazurestorage/archive/2014/02/03/windows-azure-storage-introducing-cors.aspx) 59 | 60 | [Windows Azure Storage and CORS](http://www.contentmaster.com/azure/windows-azure-storage-cors/) 61 | 62 | 63 | Special Thanks 64 | ------------- 65 | 66 | Extreme thanks goes to Gaurav Mantri and his original work using plain JavaScript. Much of it comes from the blob post... 67 | (http://gauravmantri.com/2013/02/16/uploading-large-files-in-windows-azure-blob-storage-using-shared-access-signature-html-and-javascript). I took his original code from here and turned it into a re-usable angular service. 68 | 69 | 70 | Issues & Contribution 71 | ------------- 72 | 73 | For questions, bug reports, and feature request please search through existing [issue](https://github.com/kinstephen/angular-azure-blob-upload/issues) and if you don't find and answer open a new one [here](https://github.com/kinstephen/angular-azure-blob-upload/issues/new). If you need support contact me on twitter at @kinstephen 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /azure-blob-upload.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * AngularJS Azure blob upload service with http post and progress. 3 | * @author Stephen Brannan - twitter: @kinstephen 4 | * @version 1.0.1 5 | */ 6 | (function () { 7 | 8 | var azureBlobUpload = angular.module('azureBlobUpload', []); 9 | 10 | azureBlobUpload.factory('azureBlob', 11 | ['$log', '$http', azureBlob]); 12 | 13 | function azureBlob($log, $http) { 14 | 15 | var DefaultBlockSize = 1024 * 32 // Default to 32KB 16 | 17 | /* config: { 18 | baseUrl: // baseUrl for blob file uri (i.e. http://.blob.core.windows.net//), 19 | sasToken: // Shared access signature querystring key/value prefixed with ?, 20 | file: // File object using the HTML5 File API, 21 | progress: // progress callback function, 22 | complete: // complete callback function, 23 | error: // error callback function, 24 | blockSize: // Use this to override the DefaultBlockSize 25 | } */ 26 | var upload = function (config) { 27 | var state = initializeState(config); 28 | 29 | var reader = new FileReader(); 30 | reader.onloadend = function (evt) { 31 | if (evt.target.readyState == FileReader.DONE && !state.cancelled) { // DONE == 2 32 | var uri = state.fileUrl + '&comp=block&blockid=' + state.blockIds[state.blockIds.length - 1]; 33 | var requestData = new Uint8Array(evt.target.result); 34 | 35 | $log.log(uri); 36 | $http.put(uri, requestData, 37 | { 38 | headers: { 39 | 'x-ms-blob-type': 'BlockBlob', 40 | 'Content-Type': state.file.type, 41 | }, 42 | transformRequest: [], 43 | }).then(function (response) { 44 | $log.log(response.data); 45 | $log.log(response.status); 46 | state.bytesUploaded += requestData.length; 47 | 48 | var percentComplete = ((parseFloat(state.bytesUploaded) / parseFloat(state.file.size)) * 100).toFixed(2); 49 | if (state.progress) state.progress(percentComplete, response.data, response.status, response.headers, response.config); 50 | 51 | uploadFileInBlocks(reader, state); 52 | }, function (response) { 53 | $log.log(response.data); 54 | $log.log(response.status); 55 | 56 | if (state.error) state.error(response.data, response.status, response.headers, response.config); 57 | }); 58 | } 59 | }; 60 | 61 | uploadFileInBlocks(reader, state); 62 | 63 | return { 64 | cancel: function () { 65 | state.cancelled = true; 66 | } 67 | }; 68 | }; 69 | 70 | var initializeState = function (config) { 71 | var blockSize = DefaultBlockSize; 72 | if (config.blockSize) blockSize = config.blockSize; 73 | 74 | var maxBlockSize = blockSize; // Default Block Size 75 | var numberOfBlocks = 1; 76 | 77 | var file = config.file; 78 | 79 | var fileSize = file.size; 80 | if (fileSize < blockSize) { 81 | maxBlockSize = fileSize; 82 | $log.log("max block size = " + maxBlockSize); 83 | } 84 | 85 | if (fileSize % maxBlockSize == 0) { 86 | numberOfBlocks = fileSize / maxBlockSize; 87 | } else { 88 | numberOfBlocks = parseInt(fileSize / maxBlockSize, 10) + 1; 89 | } 90 | 91 | $log.log("total blocks = " + numberOfBlocks); 92 | 93 | return { 94 | maxBlockSize: maxBlockSize, //Each file will be split in 256 KB. 95 | numberOfBlocks: numberOfBlocks, 96 | totalBytesRemaining: fileSize, 97 | currentFilePointer: 0, 98 | blockIds: new Array(), 99 | blockIdPrefix: 'block-', 100 | bytesUploaded: 0, 101 | submitUri: null, 102 | file: file, 103 | baseUrl: config.baseUrl, 104 | sasToken: config.sasToken, 105 | fileUrl: config.baseUrl + config.sasToken, 106 | progress: config.progress, 107 | complete: config.complete, 108 | error: config.error, 109 | cancelled: false 110 | }; 111 | }; 112 | 113 | var uploadFileInBlocks = function (reader, state) { 114 | if (!state.cancelled) { 115 | if (state.totalBytesRemaining > 0) { 116 | $log.log("current file pointer = " + state.currentFilePointer + " bytes read = " + state.maxBlockSize); 117 | 118 | var fileContent = state.file.slice(state.currentFilePointer, state.currentFilePointer + state.maxBlockSize); 119 | var blockId = state.blockIdPrefix + pad(state.blockIds.length, 6); 120 | $log.log("block id = " + blockId); 121 | 122 | state.blockIds.push(btoa(blockId)); 123 | reader.readAsArrayBuffer(fileContent); 124 | 125 | state.currentFilePointer += state.maxBlockSize; 126 | state.totalBytesRemaining -= state.maxBlockSize; 127 | if (state.totalBytesRemaining < state.maxBlockSize) { 128 | state.maxBlockSize = state.totalBytesRemaining; 129 | } 130 | } else { 131 | commitBlockList(state); 132 | } 133 | } 134 | }; 135 | 136 | var commitBlockList = function (state) { 137 | var uri = state.fileUrl + '&comp=blocklist'; 138 | $log.log(uri); 139 | 140 | var requestBody = ''; 141 | for (var i = 0; i < state.blockIds.length; i++) { 142 | requestBody += '' + state.blockIds[i] + ''; 143 | } 144 | requestBody += ''; 145 | $log.log(requestBody); 146 | 147 | $http.put(uri, requestBody, 148 | { 149 | headers: { 150 | 'x-ms-blob-content-type': state.file.type, 151 | } 152 | }).then(function (response) { 153 | $log.log(response.data); 154 | $log.log(response.status); 155 | if (state.complete) state.complete(response.data, response.status, response.headers, response.config); 156 | }, function (response) { 157 | $log.log(response.data); 158 | $log.log(response.status); 159 | if (state.error) state.error(response.data, response.status, response.headers, response.config); 160 | }); 161 | }; 162 | 163 | var pad = function (number, length) { 164 | var str = '' + number; 165 | while (str.length < length) { 166 | str = '0' + str; 167 | } 168 | return str; 169 | }; 170 | 171 | return { 172 | upload: upload, 173 | }; 174 | }; 175 | 176 | })(); 177 | --------------------------------------------------------------------------------