@webex/http-core 3.0.0-beta.8 → 3.0.0-beta.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +0 -1
  2. package/dist/http-error-subtypes.js +2 -147
  3. package/dist/http-error-subtypes.js.map +1 -1
  4. package/dist/http-error.js +9 -38
  5. package/dist/http-error.js.map +1 -1
  6. package/dist/index.js +5 -29
  7. package/dist/index.js.map +1 -1
  8. package/dist/interceptors/http-status.js +7 -30
  9. package/dist/interceptors/http-status.js.map +1 -1
  10. package/dist/lib/detect.js +28 -48
  11. package/dist/lib/detect.js.map +1 -1
  12. package/dist/lib/interceptor.js +7 -23
  13. package/dist/lib/interceptor.js.map +1 -1
  14. package/dist/lib/xhr.js +44 -93
  15. package/dist/lib/xhr.js.map +1 -1
  16. package/dist/progress-event.js +0 -7
  17. package/dist/progress-event.js.map +1 -1
  18. package/dist/request/index.js +1 -15
  19. package/dist/request/index.js.map +1 -1
  20. package/dist/request/request.js +5 -23
  21. package/dist/request/request.js.map +1 -1
  22. package/dist/request/request.shim.js +38 -90
  23. package/dist/request/request.shim.js.map +1 -1
  24. package/package.json +10 -10
  25. package/src/http-error-subtypes.js +1 -1
  26. package/src/http-error.js +15 -23
  27. package/src/index.js +4 -9
  28. package/src/interceptors/http-status.js +7 -5
  29. package/src/lib/detect.js +0 -1
  30. package/src/lib/interceptor.js +2 -4
  31. package/src/lib/xhr.js +197 -194
  32. package/src/progress-event.js +10 -5
  33. package/src/request/request.js +16 -14
  34. package/src/request/request.shim.js +47 -38
  35. package/test/integration/spec/http-error.js +11 -11
  36. package/test/integration/spec/interceptor.js +20 -13
  37. package/test/integration/spec/progress-event.js +8 -8
  38. package/test/integration/spec/request.js +135 -127
  39. package/test/unit/spec/interceptors/http-status.js +14 -11
package/src/http-error.js CHANGED
@@ -23,14 +23,7 @@ export default class HttpError extends Exception {
23
23
  *
24
24
  * @type {Array}
25
25
  */
26
- static errorKeys = [
27
- 'error',
28
- 'errorString',
29
- 'response',
30
- 'errorResponse',
31
- 'message',
32
- 'msg'
33
- ];
26
+ static errorKeys = ['error', 'errorString', 'response', 'errorResponse', 'message', 'msg'];
34
27
 
35
28
  /**
36
29
  * Default error string if no error can be extracted from the http response
@@ -55,8 +48,7 @@ export default class HttpError extends Exception {
55
48
  try {
56
49
  body = JSON.parse(body);
57
50
  message = this.parseObject(body);
58
- }
59
- catch (err) {
51
+ } catch (err) {
60
52
  message = body;
61
53
  }
62
54
  break;
@@ -73,52 +65,52 @@ export default class HttpError extends Exception {
73
65
  Object.defineProperties(this, {
74
66
  body: {
75
67
  enumerable: false,
76
- value: body
68
+ value: body,
77
69
  },
78
70
  httpVersion: {
79
71
  enumerable: false,
80
- value: res.httpVersion
72
+ value: res.httpVersion,
81
73
  },
82
74
  headers: {
83
75
  enumerable: false,
84
- value: res.headers || {}
76
+ value: res.headers || {},
85
77
  },
86
78
  rawHeaders: {
87
79
  enumerable: false,
88
- value: res.rawHeaders || []
80
+ value: res.rawHeaders || [],
89
81
  },
90
82
  trailers: {
91
83
  enumerable: false,
92
- value: res.trailers || {}
84
+ value: res.trailers || {},
93
85
  },
94
86
  rawTrailers: {
95
87
  enumerable: false,
96
- value: res.rawTrailers || []
88
+ value: res.rawTrailers || [],
97
89
  },
98
90
  method: {
99
91
  enumerable: false,
100
- value: res.method
92
+ value: res.method,
101
93
  },
102
94
  url: {
103
95
  enumerable: false,
104
- value: res.url
96
+ value: res.url,
105
97
  },
106
98
  statusCode: {
107
99
  enumerable: false,
108
- value: res.statusCode
100
+ value: res.statusCode,
109
101
  },
110
102
  statusMessage: {
111
103
  enumerable: false,
112
- value: res.statusMessage
104
+ value: res.statusMessage,
113
105
  },
114
106
  socket: {
115
107
  enumerable: false,
116
- value: res.socket
108
+ value: res.socket,
117
109
  },
118
110
  _res: {
119
111
  enumerable: false,
120
- value: res
121
- }
112
+ value: res,
113
+ },
122
114
  });
123
115
 
124
116
  return message;
package/src/index.js CHANGED
@@ -20,17 +20,12 @@ const protorequest = curry(function protorequest(defaultOptions, options) {
20
20
  }
21
21
 
22
22
  // Hide useless elements from logs
23
- [
24
- 'download',
25
- 'interceptors',
26
- 'logger',
27
- 'upload'
28
- ].forEach((prop) => {
23
+ ['download', 'interceptors', 'logger', 'upload'].forEach((prop) => {
29
24
  let descriptor = Reflect.getOwnPropertyDescriptor(options, prop);
30
25
 
31
26
  descriptor = assign({}, descriptor, {
32
27
  enumerable: false,
33
- writable: true
28
+ writable: true,
34
29
  });
35
30
  Reflect.defineProperty(options, prop, descriptor);
36
31
  });
@@ -50,8 +45,8 @@ const defaultOptions = {
50
45
  json: true,
51
46
  interceptors: [
52
47
  // Reminder: this is supposed to be an instantiated interceptor.
53
- HttpStatusInterceptor.create()
54
- ]
48
+ HttpStatusInterceptor.create(),
49
+ ],
55
50
  };
56
51
 
57
52
  export const defaults = protorequest;
@@ -18,12 +18,12 @@ export default class HttpStatusInterceptor extends Interceptor {
18
18
  */
19
19
  constructor(webex, options) {
20
20
  super(webex);
21
- const ErrorConstructor = options && (options.error || options.ErrorConstructor) || HttpError;
21
+ const ErrorConstructor = (options && (options.error || options.ErrorConstructor)) || HttpError;
22
22
 
23
23
  Object.defineProperties(this, {
24
24
  ErrorConstructor: {
25
- value: ErrorConstructor
26
- }
25
+ value: ErrorConstructor,
26
+ },
27
27
  });
28
28
  }
29
29
 
@@ -46,9 +46,11 @@ export default class HttpStatusInterceptor extends Interceptor {
46
46
  return Promise.resolve(response);
47
47
  }
48
48
  // to handle locus redirects
49
- if (response.statusCode === 404 &&
49
+ if (
50
+ response.statusCode === 404 &&
50
51
  response.body &&
51
- response.body.errorCode === LOCUS_REDIRECT_ERROR) {
52
+ response.body.errorCode === LOCUS_REDIRECT_ERROR
53
+ ) {
52
54
  return Promise.resolve(response);
53
55
  }
54
56
  }
package/src/lib/detect.js CHANGED
@@ -10,7 +10,6 @@ import {fromBuffer} from 'file-type';
10
10
  * @returns {Promise<string>}
11
11
  */
12
12
  export default async function detect(buffer) {
13
- /* global Blob */
14
13
  if (
15
14
  !(buffer instanceof Blob) &&
16
15
  !(buffer instanceof ArrayBuffer) &&
@@ -20,7 +20,7 @@ export default class Interceptor {
20
20
 
21
21
  Reflect.defineProperty(this, key, {
22
22
  enumerable: true,
23
- value
23
+ value,
24
24
  });
25
25
  });
26
26
  }
@@ -43,9 +43,7 @@ export default class Interceptor {
43
43
  // prepend a header for the interceptor
44
44
  logger.info('/***** Interceptor ****************************************************\\');
45
45
 
46
- logger.info(
47
- `${this.constructor.name} - ${JSON.stringify(options, null, 2)}`
48
- );
46
+ logger.info(`${this.constructor.name} - ${JSON.stringify(options, null, 2)}`);
49
47
  }
50
48
 
51
49
  /**
package/src/lib/xhr.js CHANGED
@@ -11,243 +11,246 @@
11
11
  // we're trying to diverge as little as possible.
12
12
  /* eslint-disable */
13
13
 
14
- "use strict";
15
- var window = require("global/window")
16
- var isFunction = require("is-function")
17
- var parseHeaders = require("parse-headers")
18
- var xtend = require("xtend")
19
-
20
- createXHR.XMLHttpRequest = window.XMLHttpRequest || noop
21
- createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest
22
-
23
- forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) {
24
- createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) {
25
- options = initParams(uri, options, callback)
26
- options.method = method.toUpperCase()
27
- return _createXHR(options)
28
- }
29
- })
14
+ 'use strict';
15
+ var window = require('global/window');
16
+ var isFunction = require('is-function');
17
+ var parseHeaders = require('parse-headers');
18
+ var xtend = require('xtend');
19
+
20
+ createXHR.XMLHttpRequest = window.XMLHttpRequest || noop;
21
+ createXHR.XDomainRequest =
22
+ 'withCredentials' in new createXHR.XMLHttpRequest()
23
+ ? createXHR.XMLHttpRequest
24
+ : window.XDomainRequest;
25
+
26
+ forEachArray(['get', 'put', 'post', 'patch', 'head', 'delete'], function (method) {
27
+ createXHR[method === 'delete' ? 'del' : method] = function (uri, options, callback) {
28
+ options = initParams(uri, options, callback);
29
+ options.method = method.toUpperCase();
30
+ return _createXHR(options);
31
+ };
32
+ });
30
33
 
31
34
  function forEachArray(array, iterator) {
32
- for (var i = 0; i < array.length; i+= 1) {
33
- iterator(array[i])
34
- }
35
+ for (var i = 0; i < array.length; i += 1) {
36
+ iterator(array[i]);
37
+ }
35
38
  }
36
39
 
37
- function isEmpty(obj){
38
- for(var i in obj){
39
- if(obj.hasOwnProperty(i)) return false
40
- }
41
- return true
40
+ function isEmpty(obj) {
41
+ for (var i in obj) {
42
+ if (obj.hasOwnProperty(i)) return false;
43
+ }
44
+ return true;
42
45
  }
43
46
 
44
47
  function initParams(uri, options, callback) {
45
- var params = uri
48
+ var params = uri;
46
49
 
47
- if (isFunction(options)) {
48
- callback = options
49
- if (typeof uri === "string") {
50
- params = {uri:uri}
51
- }
52
- } else {
53
- params = xtend(options, {uri: uri})
50
+ if (isFunction(options)) {
51
+ callback = options;
52
+ if (typeof uri === 'string') {
53
+ params = {uri: uri};
54
54
  }
55
+ } else {
56
+ params = xtend(options, {uri: uri});
57
+ }
55
58
 
56
- params.callback = callback
57
- return params
59
+ params.callback = callback;
60
+ return params;
58
61
  }
59
62
 
60
63
  function createXHR(uri, options, callback) {
61
- options = initParams(uri, options, callback)
62
- return _createXHR(options)
64
+ options = initParams(uri, options, callback);
65
+ return _createXHR(options);
63
66
  }
64
67
 
65
68
  function _createXHR(options) {
66
- if(typeof options.callback === "undefined"){
67
- throw new Error("callback argument missing")
69
+ if (typeof options.callback === 'undefined') {
70
+ throw new Error('callback argument missing');
71
+ }
72
+
73
+ var called = false;
74
+ var callback = function cbOnce(err, response, body) {
75
+ if (!called) {
76
+ called = true;
77
+ options.callback(err, response, body);
68
78
  }
79
+ };
69
80
 
70
- var called = false
71
- var callback = function cbOnce(err, response, body){
72
- if(!called){
73
- called = true
74
- options.callback(err, response, body)
75
- }
81
+ function readystatechange() {
82
+ if (xhr.readyState === 4) {
83
+ setTimeout(loadFunc, 0);
76
84
  }
85
+ }
77
86
 
78
- function readystatechange() {
79
- if (xhr.readyState === 4) {
80
- setTimeout(loadFunc, 0)
81
- }
82
- }
83
-
84
- function getBody() {
85
- // Chrome with requestType=blob throws errors arround when even testing access to responseText
86
- var body = undefined
87
-
88
- if (xhr.response) {
89
- body = xhr.response
90
- } else {
91
- body = xhr.responseText || getXml(xhr)
92
- }
87
+ function getBody() {
88
+ // Chrome with requestType=blob throws errors arround when even testing access to responseText
89
+ var body = undefined;
93
90
 
94
- if (isJson) {
95
- try {
96
- body = JSON.parse(body)
97
- } catch (e) {}
98
- }
99
-
100
- return body
101
- }
102
-
103
- function errorFunc(evt) {
104
- clearTimeout(timeoutTimer)
105
- if(!(evt instanceof Error)){
106
- evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") )
107
- }
108
- evt.statusCode = 0
109
- return callback(evt, failureResponse)
91
+ if (xhr.response) {
92
+ body = xhr.response;
93
+ } else {
94
+ body = xhr.responseText || getXml(xhr);
110
95
  }
111
96
 
112
- // will load the data & process the response in a special response object
113
- function loadFunc() {
114
- if (aborted) return
115
- var status
116
- clearTimeout(timeoutTimer)
117
- if(options.useXDR && xhr.status===undefined) {
118
- //IE8 CORS GET successful response doesn't have a status field, but body is fine
119
- status = 200
120
- } else {
121
- status = (xhr.status === 1223 ? 204 : xhr.status)
122
- }
123
- var response = failureResponse
124
- var err = null
125
-
126
- if (status !== 0){
127
- response = {
128
- body: getBody(),
129
- statusCode: status,
130
- method: method,
131
- headers: {},
132
- url: uri,
133
- rawRequest: xhr
134
- }
135
- if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE
136
- response.headers = parseHeaders(xhr.getAllResponseHeaders())
137
- }
138
- } else {
139
- err = new Error("Internal XMLHttpRequest Error")
140
- }
141
- return callback(err, response, response.body)
97
+ if (isJson) {
98
+ try {
99
+ body = JSON.parse(body);
100
+ } catch (e) {}
142
101
  }
143
102
 
144
- var xhr = options.xhr || null
103
+ return body;
104
+ }
145
105
 
146
- if (!xhr) {
147
- if (options.cors || options.useXDR) {
148
- xhr = new createXHR.XDomainRequest()
149
- }else{
150
- xhr = new createXHR.XMLHttpRequest()
151
- }
106
+ function errorFunc(evt) {
107
+ clearTimeout(timeoutTimer);
108
+ if (!(evt instanceof Error)) {
109
+ evt = new Error('' + (evt || 'Unknown XMLHttpRequest Error'));
152
110
  }
111
+ evt.statusCode = 0;
112
+ return callback(evt, failureResponse);
113
+ }
114
+
115
+ // will load the data & process the response in a special response object
116
+ function loadFunc() {
117
+ if (aborted) return;
118
+ var status;
119
+ clearTimeout(timeoutTimer);
120
+ if (options.useXDR && xhr.status === undefined) {
121
+ //IE8 CORS GET successful response doesn't have a status field, but body is fine
122
+ status = 200;
123
+ } else {
124
+ status = xhr.status === 1223 ? 204 : xhr.status;
125
+ }
126
+ var response = failureResponse;
127
+ var err = null;
153
128
 
154
- var key
155
- var aborted
156
- var uri = options.uri || options.url
157
- var method = options.method || "GET"
158
- var body = options.body || options.data
159
- var headers = options.headers || {}
160
- var sync = !!options.sync
161
- var isJson = false
162
- var timeoutTimer
163
- var failureResponse = {
164
- body: undefined,
165
- headers: {},
166
- statusCode: 0,
129
+ if (status !== 0) {
130
+ response = {
131
+ body: getBody(),
132
+ statusCode: status,
167
133
  method: method,
134
+ headers: {},
168
135
  url: uri,
169
- rawRequest: xhr
170
- }
171
-
172
- if ("json" in options && options.json !== false) {
173
- isJson = true
174
- headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json") //Don't override existing accept header declared by user
175
- if (method !== "GET" && method !== "HEAD") {
176
- headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json") //Don't override existing accept header declared by user
177
- body = JSON.stringify(options.json === true ? body : options.json)
178
- }
136
+ rawRequest: xhr,
137
+ };
138
+ if (xhr.getAllResponseHeaders) {
139
+ //remember xhr can in fact be XDR for CORS in IE
140
+ response.headers = parseHeaders(xhr.getAllResponseHeaders());
141
+ }
142
+ } else {
143
+ err = new Error('Internal XMLHttpRequest Error');
179
144
  }
145
+ return callback(err, response, response.body);
146
+ }
180
147
 
181
- xhr.onreadystatechange = readystatechange
182
- xhr.onload = loadFunc
183
- xhr.onerror = errorFunc
184
- // IE9 must have onprogress be set to a unique function.
185
- xhr.onprogress = function () {
186
- // IE must die
187
- }
188
- xhr.onabort = function(){
189
- aborted = true;
190
- }
191
- xhr.ontimeout = errorFunc
192
- xhr.open(method, uri, !sync, options.username, options.password)
193
- //has to be after open
194
- if(!sync) {
195
- xhr.withCredentials = !!options.withCredentials
196
- }
197
- // Cannot set timeout with sync request
198
- // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
199
- // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
200
- if (!sync && options.timeout > 0 ) {
201
- timeoutTimer = setTimeout(function(){
202
- if (aborted) return
203
- aborted = true//IE9 may still call readystatechange
204
- xhr.abort("timeout")
205
- var e = new Error("XMLHttpRequest timeout")
206
- e.code = "ETIMEDOUT"
207
- errorFunc(e)
208
- }, options.timeout )
209
- }
148
+ var xhr = options.xhr || null;
210
149
 
211
- if (xhr.setRequestHeader) {
212
- for(key in headers){
213
- if(headers.hasOwnProperty(key)){
214
- xhr.setRequestHeader(key, headers[key])
215
- }
216
- }
217
- } else if (options.headers && !isEmpty(options.headers)) {
218
- throw new Error("Headers cannot be set on an XDomainRequest object")
150
+ if (!xhr) {
151
+ if (options.cors || options.useXDR) {
152
+ xhr = new createXHR.XDomainRequest();
153
+ } else {
154
+ xhr = new createXHR.XMLHttpRequest();
219
155
  }
220
-
221
- if ("responseType" in options) {
222
- xhr.responseType = options.responseType
156
+ }
157
+
158
+ var key;
159
+ var aborted;
160
+ var uri = options.uri || options.url;
161
+ var method = options.method || 'GET';
162
+ var body = options.body || options.data;
163
+ var headers = options.headers || {};
164
+ var sync = !!options.sync;
165
+ var isJson = false;
166
+ var timeoutTimer;
167
+ var failureResponse = {
168
+ body: undefined,
169
+ headers: {},
170
+ statusCode: 0,
171
+ method: method,
172
+ url: uri,
173
+ rawRequest: xhr,
174
+ };
175
+
176
+ if ('json' in options && options.json !== false) {
177
+ isJson = true;
178
+ headers['accept'] || headers['Accept'] || (headers['Accept'] = 'application/json'); //Don't override existing accept header declared by user
179
+ if (method !== 'GET' && method !== 'HEAD') {
180
+ headers['content-type'] ||
181
+ headers['Content-Type'] ||
182
+ (headers['Content-Type'] = 'application/json'); //Don't override existing accept header declared by user
183
+ body = JSON.stringify(options.json === true ? body : options.json);
223
184
  }
224
-
225
- if ("beforeSend" in options &&
226
- typeof options.beforeSend === "function"
227
- ) {
228
- options.beforeSend(xhr)
185
+ }
186
+
187
+ xhr.onreadystatechange = readystatechange;
188
+ xhr.onload = loadFunc;
189
+ xhr.onerror = errorFunc;
190
+ // IE9 must have onprogress be set to a unique function.
191
+ xhr.onprogress = function () {
192
+ // IE must die
193
+ };
194
+ xhr.onabort = function () {
195
+ aborted = true;
196
+ };
197
+ xhr.ontimeout = errorFunc;
198
+ xhr.open(method, uri, !sync, options.username, options.password);
199
+ //has to be after open
200
+ if (!sync) {
201
+ xhr.withCredentials = !!options.withCredentials;
202
+ }
203
+ // Cannot set timeout with sync request
204
+ // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
205
+ // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
206
+ if (!sync && options.timeout > 0) {
207
+ timeoutTimer = setTimeout(function () {
208
+ if (aborted) return;
209
+ aborted = true; //IE9 may still call readystatechange
210
+ xhr.abort('timeout');
211
+ var e = new Error('XMLHttpRequest timeout');
212
+ e.code = 'ETIMEDOUT';
213
+ errorFunc(e);
214
+ }, options.timeout);
215
+ }
216
+
217
+ if (xhr.setRequestHeader) {
218
+ for (key in headers) {
219
+ if (headers.hasOwnProperty(key)) {
220
+ xhr.setRequestHeader(key, headers[key]);
221
+ }
229
222
  }
223
+ } else if (options.headers && !isEmpty(options.headers)) {
224
+ throw new Error('Headers cannot be set on an XDomainRequest object');
225
+ }
230
226
 
231
- // Microsoft Edge browser sends "undefined" when send is called with undefined value.
232
- // XMLHttpRequest spec says to pass null as body to indicate no body
233
- // See https://github.com/naugtur/xhr/issues/100.
234
- xhr.send(body || null)
227
+ if ('responseType' in options) {
228
+ xhr.responseType = options.responseType;
229
+ }
235
230
 
236
- return xhr
231
+ if ('beforeSend' in options && typeof options.beforeSend === 'function') {
232
+ options.beforeSend(xhr);
233
+ }
237
234
 
235
+ // Microsoft Edge browser sends "undefined" when send is called with undefined value.
236
+ // XMLHttpRequest spec says to pass null as body to indicate no body
237
+ // See https://github.com/naugtur/xhr/issues/100.
238
+ xhr.send(body || null);
238
239
 
240
+ return xhr;
239
241
  }
240
242
 
241
243
  function getXml(xhr) {
242
- if (xhr.responseType === "document") {
243
- return xhr.responseXML
244
- }
245
- var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"
246
- if (xhr.responseType === "" && !firefoxBugTakenEffect) {
247
- return xhr.responseXML
248
- }
249
-
250
- return null
244
+ if (xhr.responseType === 'document') {
245
+ return xhr.responseXML;
246
+ }
247
+ var firefoxBugTakenEffect =
248
+ xhr.responseXML && xhr.responseXML.documentElement.nodeName === 'parsererror';
249
+ if (xhr.responseType === '' && !firefoxBugTakenEffect) {
250
+ return xhr.responseXML;
251
+ }
252
+
253
+ return null;
251
254
  }
252
255
 
253
256
  function noop() {}
@@ -16,17 +16,22 @@ export default function ProgressEvent(loaded, total) {
16
16
  loaded: {
17
17
  enumerable: true,
18
18
  value: loaded,
19
- writable: false
19
+ writable: false,
20
20
  },
21
21
  total: {
22
22
  enumerable: true,
23
23
  value: total,
24
- writable: false
24
+ writable: false,
25
25
  },
26
26
  lengthComputable: {
27
27
  enumerable: true,
28
- value: isNumber(loaded) && !Number.isNaN(loaded) && isNumber(total) && !Number.isNaN(total) && total > 0,
29
- writable: false
30
- }
28
+ value:
29
+ isNumber(loaded) &&
30
+ !Number.isNaN(loaded) &&
31
+ isNumber(total) &&
32
+ !Number.isNaN(total) &&
33
+ total > 0,
34
+ writable: false,
35
+ },
31
36
  });
32
37
  }