Michael Lando | efa037d | 2017-02-19 12:57:33 +0200 | [diff] [blame] | 1 | /*- |
| 2 | * ============LICENSE_START======================================================= |
| 3 | * SDC |
| 4 | * ================================================================================ |
| 5 | * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. |
| 6 | * ================================================================================ |
| 7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | * you may not use this file except in compliance with the License. |
| 9 | * You may obtain a copy of the License at |
| 10 | * |
| 11 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | * |
| 13 | * Unless required by applicable law or agreed to in writing, software |
| 14 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | * See the License for the specific language governing permissions and |
| 17 | * limitations under the License. |
| 18 | * ============LICENSE_END========================================================= |
| 19 | */ |
| 20 | |
| 21 | import _extend from 'lodash/extend.js'; |
| 22 | import _clone from 'lodash/clone.js'; |
| 23 | import _defaults from 'lodash/defaults.js'; |
| 24 | import $ from 'jquery'; |
| 25 | import uuid from 'uuid-js'; |
| 26 | import md5 from 'md5'; |
| 27 | |
| 28 | import store from 'sdc-app/AppStore.js'; |
| 29 | import {actionTypes as LoaderConstants} from 'nfvo-components/loader/LoaderConstants.js'; |
| 30 | import Configuration from 'sdc-app/config/Configuration.js'; |
| 31 | import errorResponseHandler from './ErrorResponseHandler.js'; |
| 32 | |
| 33 | const methodMap = { |
| 34 | 'create': 'POST', |
| 35 | 'update': 'PUT', |
| 36 | 'delete': 'DELETE', |
| 37 | 'read': 'GET' |
| 38 | }; |
| 39 | const AUTHORIZATION_HEADER = 'X-AUTH-TOKEN'; |
| 40 | const STORAGE_AUTH_KEY = 'sdc-auth-token'; |
| 41 | const REQUEST_ID_HEADER = 'X-ECOMP-RequestID'; |
| 42 | const CONTENT_MD5_HEADER = 'Content-MD5'; |
| 43 | const namedParam = /{(\w+)}/g; |
| 44 | const queryParamsNames = { |
| 45 | pageStart: 'pageStart', |
| 46 | pageSize: 'pageSize', |
| 47 | sortField: 'sortField', |
| 48 | sortDir: 'sortDir', |
| 49 | filtering: 'filter' |
| 50 | }; |
| 51 | |
| 52 | |
| 53 | // jQuery binary transport to download files through XHR |
| 54 | // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ |
| 55 | // https://github.com/henrya/js-jquery/tree/master/BinaryTransport |
| 56 | $.ajaxTransport('+binary', function (options/*, originalOptions , jqXHR*/) { |
| 57 | // check for conditions and support for blob / arraybuffer response type |
| 58 | if (window.FormData && ((options.dataType && (options.dataType === 'binary')) || |
| 59 | (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || |
| 60 | (window.Blob && options.data instanceof Blob)))) |
| 61 | ) { |
| 62 | return { |
| 63 | // create new XMLHttpRequest |
| 64 | send: function (headers, callback) { |
| 65 | // setup all variables |
| 66 | var xhr = new XMLHttpRequest(), |
| 67 | url = options.url, |
| 68 | type = options.type, |
| 69 | async = options.async || true, |
| 70 | // blob or arraybuffer. Default is blob |
| 71 | dataType = options.responseType || 'blob', |
| 72 | data = options.data || null, |
| 73 | username = options.username || null, |
| 74 | password = options.password || null; |
| 75 | |
| 76 | xhr.addEventListener('load', function () { |
| 77 | var data = {}; |
| 78 | data[options.dataType] = xhr.response; |
| 79 | // make callback and send data |
| 80 | callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders()); |
| 81 | }); |
| 82 | |
| 83 | xhr.open(type, url, async, username, password); |
| 84 | |
| 85 | // setup custom headers |
| 86 | for (var i in headers) { |
| 87 | xhr.setRequestHeader(i, headers[i]); |
| 88 | } |
| 89 | |
| 90 | xhr.responseType = dataType; |
| 91 | xhr.send(data); |
| 92 | }, |
| 93 | abort: function () { |
| 94 | } |
| 95 | }; |
| 96 | } |
| 97 | }); |
| 98 | |
| 99 | $(document).ajaxStart(()=> store.dispatch({type: LoaderConstants.SHOW})); |
| 100 | $(document).ajaxStop(()=> store.dispatch({type: LoaderConstants.HIDE})); |
| 101 | |
| 102 | function urlError() { |
| 103 | throw new Error('A "url" property or function must be specified'); |
| 104 | }; |
| 105 | |
| 106 | export function makeQueryParams(options) { |
| 107 | var qParams = {}; |
| 108 | if (options.pagination) { |
| 109 | qParams[queryParamsNames.pageStart] = options.pagination.pageStart; |
| 110 | qParams[queryParamsNames.pageSize] = options.pagination.pageSize; |
| 111 | } |
| 112 | if (options.sorting) { |
| 113 | qParams[queryParamsNames.sortField] = options.sorting.sortField; |
| 114 | qParams[queryParamsNames.sortDir] = options.sorting.sortDir; |
| 115 | } |
| 116 | if (options.filtering) { |
| 117 | qParams[queryParamsNames.filtering] = JSON.stringify(options.filtering); |
| 118 | } |
| 119 | |
| 120 | return _defaults(qParams, options.qParams); |
| 121 | } |
| 122 | |
| 123 | function appendQueryParam(p, value) { |
| 124 | var str = ''; |
| 125 | |
| 126 | if (value instanceof Array) { |
| 127 | if (value.length === 1) { |
| 128 | str = appendQueryParam(p, value[0]); |
| 129 | } else if (value.length > 1) { |
| 130 | str = appendQueryParam(p, value.shift()) + '&' + appendQueryParam(p, value); |
| 131 | } |
| 132 | } else { |
| 133 | str = p + '=' + encodeURIComponent(value); |
| 134 | } |
| 135 | |
| 136 | return str; |
| 137 | } |
| 138 | |
| 139 | function appendQueryString(url, qParams) { |
| 140 | var str = ''; |
| 141 | for (var param in qParams) { |
| 142 | str += (str ? '&' : '') + appendQueryParam(param, qParams[param]); |
| 143 | } |
| 144 | return url + (str ? '?' : '') + str; |
| 145 | } |
| 146 | |
| 147 | function composeURL(baseUrl, options) { |
| 148 | var url = baseUrl || urlError(); |
| 149 | if (options.url) { |
| 150 | delete options.url; |
| 151 | } |
| 152 | |
| 153 | var qParams = makeQueryParams(options); |
| 154 | url = appendQueryString(url, qParams); |
| 155 | |
| 156 | var matches = url.match(namedParam); |
| 157 | if (matches) { |
| 158 | for (var i = 0; i < matches.length; i++) { |
| 159 | var param = matches[i].substring(1, matches[i].length - 1); |
| 160 | var value = (options.params && options.params[param]); |
| 161 | |
| 162 | if (value === undefined) { |
| 163 | value = options[param]; |
| 164 | } |
| 165 | url = url.replace(matches[i], encodeURIComponent(value)); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | return url; |
| 170 | } |
| 171 | |
| 172 | function applyMD5Header(options, data) { |
| 173 | if (options.md5) { |
| 174 | let headers = options.headers; |
| 175 | headers[CONTENT_MD5_HEADER] = window.btoa(md5(JSON.stringify(data)).toLowerCase()); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | function applySecurity(options, data) { |
| 180 | var headers = options.headers || (options.headers = {}); |
| 181 | |
| 182 | var authToken = localStorage.getItem(STORAGE_AUTH_KEY); |
| 183 | if (authToken) { |
| 184 | headers[AUTHORIZATION_HEADER] = authToken; |
| 185 | } |
| 186 | |
| 187 | var attApiHeaders = Configuration.get('ATTApiHeaders'), |
| 188 | attUidHeader = attApiHeaders && attApiHeaders.userId; |
| 189 | if (attUidHeader) { |
| 190 | headers[attUidHeader.name] = attUidHeader.value; |
| 191 | } |
| 192 | |
| 193 | headers[REQUEST_ID_HEADER] = uuid.create().toString(); |
| 194 | |
| 195 | applyMD5Header(options, data); |
| 196 | } |
| 197 | |
| 198 | function handleResponse(options) { |
| 199 | var authToken = options.xhr.getResponseHeader(AUTHORIZATION_HEADER); |
| 200 | var prevToken = options.headers && options.headers[AUTHORIZATION_HEADER]; |
| 201 | if (authToken && authToken !== prevToken) { |
| 202 | if (authToken === 'null') { |
| 203 | localStorage.removeItem(STORAGE_AUTH_KEY); |
| 204 | } else { |
| 205 | localStorage.setItem(STORAGE_AUTH_KEY, authToken); |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | function sync(baseUrl, method, options, data) { |
| 211 | |
| 212 | options = options ? _clone(options) : {}; |
| 213 | |
| 214 | var type = methodMap[method]; |
| 215 | _defaults(options || (options = {})); |
| 216 | var params = { |
| 217 | type: type, |
| 218 | dataType: 'json' |
| 219 | }; |
| 220 | params.url = composeURL(baseUrl, options); |
| 221 | |
| 222 | if ((method === 'create' || method === 'update') && data instanceof FormData) { |
| 223 | params.contentType = 'multipart/form-data'; |
| 224 | params.data = data; |
| 225 | } |
| 226 | else if (method === 'create' || method === 'update') { |
| 227 | params.contentType = 'application/json'; |
| 228 | params.data = JSON.stringify(data); |
| 229 | } |
| 230 | |
| 231 | if (params.type !== 'GET') { |
| 232 | params.processData = false; |
| 233 | } |
| 234 | var success = options.success; |
| 235 | options.success = function (resp) { |
| 236 | if (success) { |
| 237 | handleResponse(options); |
| 238 | success.call(options.context, _clone(resp), resp, options); |
| 239 | } |
| 240 | }; |
| 241 | |
| 242 | options.error = options.error || errorResponseHandler; |
| 243 | |
| 244 | if (typeof options.progressCallback === 'function' && options.fileSize) { |
| 245 | const {fileSize} = options; |
| 246 | options.xhrFields = { |
| 247 | // add listener to XMLHTTPRequest object directly for progress (jquery doesn't have this yet) |
| 248 | onprogress: function (progress) { |
| 249 | // calculate upload progress |
| 250 | let percentage = Math.floor((progress.loaded / fileSize) * 100); |
| 251 | // log upload progress to console |
| 252 | //console.log('progress', percentage); |
| 253 | options.progressCallback(percentage); |
| 254 | if (percentage === 100) { |
| 255 | console.log('DONE!'); |
| 256 | } |
| 257 | } |
| 258 | }; |
| 259 | } |
| 260 | |
| 261 | applySecurity(options, data); |
| 262 | |
| 263 | if (DEBUG) { |
| 264 | console.log('--> Making REST call (' + type + '): ' + params.url); |
| 265 | } |
| 266 | var xhr = options.xhr = $.ajax(_extend(params, options)); |
| 267 | return xhr; |
| 268 | } |
| 269 | |
| 270 | export default { |
| 271 | |
| 272 | fetch(baseUrl, options) { |
| 273 | return sync(baseUrl, 'read', options); |
| 274 | }, |
| 275 | |
| 276 | save(baseUrl, data, options) { |
| 277 | return sync(baseUrl, 'update', options, data); |
| 278 | }, |
| 279 | |
| 280 | create(baseUrl, data, options) { |
| 281 | return sync(baseUrl, 'create', options, data); |
| 282 | }, |
| 283 | |
| 284 | destroy(baseUrl, options) { |
| 285 | return sync(baseUrl, 'delete', options); |
| 286 | } |
| 287 | |
| 288 | }; |