axios 0.30.3 → 0.31.1
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.
- package/README.md +26 -7
- package/UPGRADE_GUIDE.md +2 -2
- package/dist/axios.js +98 -31
- package/dist/axios.js.map +1 -1
- package/dist/axios.min.js +1 -1
- package/dist/axios.min.js.map +1 -1
- package/dist/esm/axios.js +98 -31
- package/dist/esm/axios.js.map +1 -1
- package/dist/esm/axios.min.js +1 -1
- package/dist/esm/axios.min.js.map +1 -1
- package/index.d.ts +3 -2
- package/lib/adapters/http.js +89 -10
- package/lib/adapters/xhr.js +7 -3
- package/lib/core/AxiosError.js +2 -1
- package/lib/core/dispatchRequest.js +5 -0
- package/lib/core/mergeConfig.js +20 -10
- package/lib/defaults/index.js +6 -3
- package/lib/env/data.js +2 -2
- package/lib/helpers/AxiosURLSearchParams.js +4 -3
- package/lib/helpers/sanitizeHeaderValue.js +22 -0
- package/lib/helpers/shouldBypassProxy.js +133 -0
- package/lib/helpers/toFormData.js +14 -3
- package/lib/utils.js +20 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,12 +83,6 @@ Using npm:
|
|
|
83
83
|
$ npm install axios
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
Using bower:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
$ bower install axios
|
|
90
|
-
```
|
|
91
|
-
|
|
92
86
|
Using yarn:
|
|
93
87
|
|
|
94
88
|
```bash
|
|
@@ -336,13 +330,21 @@ These are the available config options for making requests. Only the `url` is re
|
|
|
336
330
|
|
|
337
331
|
// `params` are the URL parameters to be sent with the request
|
|
338
332
|
// Must be a plain object or a URLSearchParams object
|
|
333
|
+
// Null bytes in param values stay percent-encoded as `%00` in the resulting query string
|
|
334
|
+
// (GHSA-xhjh-pmcv-23jw) — Axios does not reverse `encodeURIComponent` output for `%00`,
|
|
335
|
+
// so null-byte injection cannot be smuggled through the serializer.
|
|
339
336
|
params: {
|
|
340
337
|
ID: 12345
|
|
341
338
|
},
|
|
342
339
|
|
|
343
340
|
// `paramsSerializer` is an optional config in charge of serializing `params`
|
|
341
|
+
// Nested objects are walked with a bounded recursion depth (GHSA-62hf-57xw-28j9):
|
|
342
|
+
// once `maxDepth` is exceeded the serializer throws `ERR_FORM_DATA_DEPTH_EXCEEDED`
|
|
343
|
+
// instead of overflowing the call stack. The same cap applies to `toFormData` when
|
|
344
|
+
// `Content-Type: multipart/form-data` triggers automatic FormData serialization.
|
|
344
345
|
paramsSerializer: {
|
|
345
|
-
indexes: null // array indexes format (null - no brackets, false - empty brackets, true - brackets with indexes)
|
|
346
|
+
indexes: null, // array indexes format (null - no brackets, false - empty brackets, true - brackets with indexes)
|
|
347
|
+
maxDepth: 100 // maximum recursion depth for nested params (default: 100, Infinity disables the limit)
|
|
346
348
|
},
|
|
347
349
|
|
|
348
350
|
// `data` is the data to be sent as the request body
|
|
@@ -400,6 +402,9 @@ These are the available config options for making requests. Only the `url` is re
|
|
|
400
402
|
xsrfHeaderName: 'X-XSRF-TOKEN', // default
|
|
401
403
|
|
|
402
404
|
// `undefined` (default) - set XSRF header only for the same origin requests
|
|
405
|
+
// Only an explicit `true` (own property on the config) will add the XSRF header for
|
|
406
|
+
// cross-origin requests. Values inherited from `Object.prototype` are ignored
|
|
407
|
+
// (GHSA-xx6v-rp6x-q39c), so a polluted prototype cannot silently enable the token.
|
|
403
408
|
withXSRFToken: boolean | undefined | ((config: AxiosRequestConfig) => boolean | undefined),
|
|
404
409
|
|
|
405
410
|
// `onUploadProgress` allows handling of progress events for uploads
|
|
@@ -415,9 +420,13 @@ These are the available config options for making requests. Only the `url` is re
|
|
|
415
420
|
},
|
|
416
421
|
|
|
417
422
|
// `maxContentLength` defines the max size of the http response content in bytes allowed in node.js
|
|
423
|
+
// Also enforced on streamed responses (`responseType: 'stream'`): bytes are counted as they
|
|
424
|
+
// arrive and the stream is aborted with an error once the cap is exceeded (GHSA-vf2m-468p-8v99).
|
|
418
425
|
maxContentLength: 2000,
|
|
419
426
|
|
|
420
427
|
// `maxBodyLength` (Node only option) defines the max size of the http request content in bytes allowed
|
|
428
|
+
// Also enforced on stream uploads: uploaded bytes are tracked and the request is aborted
|
|
429
|
+
// once the cap is exceeded, even when the native http transport is used directly.
|
|
421
430
|
maxBodyLength: 2000,
|
|
422
431
|
|
|
423
432
|
// `validateStatus` defines whether to resolve or reject the promise for a given
|
|
@@ -522,6 +531,7 @@ These are the available config options for making requests. Only the `url` is re
|
|
|
522
531
|
dots: boolean; // use dots instead of brackets format
|
|
523
532
|
metaTokens: boolean; // keep special endings like {} in parameter key
|
|
524
533
|
indexes: boolean; // array indexes format null - no brackets, false - empty brackets, true - brackets with indexes
|
|
534
|
+
maxDepth: number; // maximum recursion depth for nested objects (default: 100, Infinity disables the limit)
|
|
525
535
|
}
|
|
526
536
|
}
|
|
527
537
|
```
|
|
@@ -603,6 +613,8 @@ instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
|
|
|
603
613
|
|
|
604
614
|
Config will be merged with an order of precedence. The order is library defaults found in [lib/defaults.js](https://github.com/axios/axios/blob/master/lib/defaults/index.js#L28), then `defaults` property of the instance, and finally `config` argument for the request. The latter will take precedence over the former. Here's an example.
|
|
605
615
|
|
|
616
|
+
> Note: the merged config object is created with a null prototype (`Object.create(null)`) and only own properties of the inputs are copied across. A polluted `Object.prototype` cannot leak values (for example `transport`, `adapter`, or `transformRequest`) into the outgoing config through inheritance, and keys such as `__proto__`, `constructor`, and `prototype` are dropped during the merge.
|
|
617
|
+
|
|
606
618
|
```js
|
|
607
619
|
// Create an instance using the config defaults provided by the library
|
|
608
620
|
// At this point the timeout config value is `0` as is the default for the library
|
|
@@ -1011,6 +1023,13 @@ The back-end body-parser could potentially use this meta-information to automati
|
|
|
1011
1023
|
- `false`(default) - add empty brackets (`arr[]: 1`, `arr[]: 2`, `arr[]: 3`)
|
|
1012
1024
|
- `true` - add brackets with indexes (`arr[0]: 1`, `arr[1]: 2`, `arr[2]: 3`)
|
|
1013
1025
|
|
|
1026
|
+
- `maxDepth: number = 100` - maximum recursion depth when serializing nested objects. Throws an `AxiosError` with
|
|
1027
|
+
code `ERR_FORM_DATA_DEPTH_EXCEEDED` if the limit is exceeded. Set to `Infinity` to disable the limit.
|
|
1028
|
+
|
|
1029
|
+
> **Security note:** If your server-side code forwards client-supplied objects to axios as request `data` or `params`,
|
|
1030
|
+
> keep `maxDepth` at a reasonable value (the default of 100 is sufficient for most use cases) to prevent
|
|
1031
|
+
> denial-of-service via deeply nested payloads.
|
|
1032
|
+
|
|
1014
1033
|
Let's say we have an object like this one:
|
|
1015
1034
|
|
|
1016
1035
|
```js
|
package/UPGRADE_GUIDE.md
CHANGED
|
@@ -159,8 +159,8 @@ axios.get('some/url')
|
|
|
159
159
|
Previous versions of axios shipped with an AMD, CommonJS, and Global build. This has all been rolled into a single UMD build.
|
|
160
160
|
|
|
161
161
|
```js
|
|
162
|
-
// AMD
|
|
163
|
-
require(['
|
|
162
|
+
// AMD (for example with a RequireJS path mapped to `axios/dist/axios`)
|
|
163
|
+
require(['axios/dist/axios'], function (axios) {
|
|
164
164
|
/* ... */
|
|
165
165
|
});
|
|
166
166
|
|
package/dist/axios.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// axios v0.
|
|
1
|
+
// axios v0.31.1 Copyright (c) 2026 Matt Zabriskie
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
@@ -140,7 +140,15 @@
|
|
|
140
140
|
* @return {boolean} True if value is a empty Object, otherwise false
|
|
141
141
|
*/
|
|
142
142
|
function isEmptyObject(val) {
|
|
143
|
-
|
|
143
|
+
if (!isPlainObject(val)) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
for (var key in val) {
|
|
147
|
+
if (Object.prototype.hasOwnProperty.call(val, key)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
/**
|
|
@@ -207,11 +215,17 @@
|
|
|
207
215
|
*/
|
|
208
216
|
function isFormData(thing) {
|
|
209
217
|
var pattern = '[object FormData]';
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
);
|
|
218
|
+
if (!thing) return false;
|
|
219
|
+
if (typeof FormData === 'function' && thing instanceof FormData) return true;
|
|
220
|
+
// Reject non-objects (strings, numbers, booleans) up front — Object.getPrototypeOf
|
|
221
|
+
// throws a TypeError on primitives in ES5 environments.
|
|
222
|
+
if (!isObject(thing)) return false;
|
|
223
|
+
// Reject plain objects inheriting directly from Object.prototype so prototype-pollution gadgets can't spoof FormData (GHSA-6chq-wfr3-2hj9).
|
|
224
|
+
var proto = Object.getPrototypeOf(thing);
|
|
225
|
+
if (!proto || proto === Object.prototype) return false;
|
|
226
|
+
if (!isFunction(thing.append)) return false;
|
|
227
|
+
return toString.call(thing) === pattern ||
|
|
228
|
+
(isFunction(thing.toString) && thing.toString() === pattern);
|
|
215
229
|
}
|
|
216
230
|
|
|
217
231
|
/**
|
|
@@ -598,7 +612,8 @@
|
|
|
598
612
|
'ERR_BAD_REQUEST',
|
|
599
613
|
'ERR_CANCELED',
|
|
600
614
|
'ERR_NOT_SUPPORT',
|
|
601
|
-
'ERR_INVALID_URL'
|
|
615
|
+
'ERR_INVALID_URL',
|
|
616
|
+
'ERR_FORM_DATA_DEPTH_EXCEEDED'
|
|
602
617
|
// eslint-disable-next-line func-names
|
|
603
618
|
].forEach(function(code) {
|
|
604
619
|
descriptors[code] = {value: code};
|
|
@@ -699,6 +714,7 @@
|
|
|
699
714
|
var dots = options.dots;
|
|
700
715
|
var indexes = options.indexes;
|
|
701
716
|
var _Blob = options.Blob || typeof Blob !== 'undefined' && Blob;
|
|
717
|
+
var maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth;
|
|
702
718
|
var useBlob = _Blob && isSpecCompliant(formData);
|
|
703
719
|
|
|
704
720
|
if (!utils.isFunction(visitor)) {
|
|
@@ -775,9 +791,19 @@
|
|
|
775
791
|
isVisitable: isVisitable
|
|
776
792
|
});
|
|
777
793
|
|
|
778
|
-
function build(value, path) {
|
|
794
|
+
function build(value, path, depth) {
|
|
779
795
|
if (utils.isUndefined(value)) return;
|
|
780
796
|
|
|
797
|
+
// eslint-disable-next-line no-param-reassign
|
|
798
|
+
depth = depth || 0;
|
|
799
|
+
|
|
800
|
+
if (depth > maxDepth) {
|
|
801
|
+
throw new AxiosError_1(
|
|
802
|
+
'Maximum object depth of ' + maxDepth + ' exceeded (got ' + depth + ' levels)',
|
|
803
|
+
AxiosError_1.ERR_FORM_DATA_DEPTH_EXCEEDED
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
|
|
781
807
|
if (stack.indexOf(value) !== -1) {
|
|
782
808
|
throw Error('Circular reference detected in ' + path.join('.'));
|
|
783
809
|
}
|
|
@@ -790,7 +816,7 @@
|
|
|
790
816
|
);
|
|
791
817
|
|
|
792
818
|
if (result === true) {
|
|
793
|
-
build(el, path ? path.concat(key) : [key]);
|
|
819
|
+
build(el, path ? path.concat(key) : [key], depth + 1);
|
|
794
820
|
}
|
|
795
821
|
});
|
|
796
822
|
|
|
@@ -801,7 +827,7 @@
|
|
|
801
827
|
throw new TypeError('data must be an object');
|
|
802
828
|
}
|
|
803
829
|
|
|
804
|
-
build(obj);
|
|
830
|
+
build(obj, null, 0);
|
|
805
831
|
|
|
806
832
|
return formData;
|
|
807
833
|
}
|
|
@@ -809,16 +835,17 @@
|
|
|
809
835
|
var toFormData_1 = toFormData;
|
|
810
836
|
|
|
811
837
|
function encode$1(str) {
|
|
838
|
+
// Do not map `%00` back to a raw null byte (GHSA-xhjh-pmcv-23jw): that reversed
|
|
839
|
+
// the safe percent-encoding from encodeURIComponent and enabled null byte injection.
|
|
812
840
|
var charMap = {
|
|
813
841
|
'!': '%21',
|
|
814
842
|
"'": '%27',
|
|
815
843
|
'(': '%28',
|
|
816
844
|
')': '%29',
|
|
817
845
|
'~': '%7E',
|
|
818
|
-
'%20': '+'
|
|
819
|
-
'%00': '\x00'
|
|
846
|
+
'%20': '+'
|
|
820
847
|
};
|
|
821
|
-
return encodeURIComponent(str).replace(/[!'\(\)~]|%20
|
|
848
|
+
return encodeURIComponent(str).replace(/[!'\(\)~]|%20/g, function replacer(match) {
|
|
822
849
|
return charMap[match];
|
|
823
850
|
});
|
|
824
851
|
}
|
|
@@ -1335,7 +1362,8 @@
|
|
|
1335
1362
|
var requestData = config.data;
|
|
1336
1363
|
var requestHeaders = config.headers;
|
|
1337
1364
|
var responseType = config.responseType;
|
|
1338
|
-
|
|
1365
|
+
// Guard against prototype pollution (GHSA-xx6v-rp6x-q39c): only honor own properties.
|
|
1366
|
+
var withXSRFToken = utils.hasOwnProperty(config, 'withXSRFToken') ? config.withXSRFToken : undefined;
|
|
1339
1367
|
var onCanceled;
|
|
1340
1368
|
function done() {
|
|
1341
1369
|
if (config.cancelToken) {
|
|
@@ -1463,8 +1491,11 @@
|
|
|
1463
1491
|
// Specifically not if we're in a web worker, or react-native.
|
|
1464
1492
|
if (utils.isStandardBrowserEnv()) {
|
|
1465
1493
|
// Add xsrf header
|
|
1466
|
-
|
|
1467
|
-
|
|
1494
|
+
if (utils.isFunction(withXSRFToken)) {
|
|
1495
|
+
withXSRFToken = withXSRFToken(config);
|
|
1496
|
+
}
|
|
1497
|
+
// Strict boolean check (GHSA-xx6v-rp6x-q39c): only `true` short-circuits the same-origin guard.
|
|
1498
|
+
if (withXSRFToken === true || (withXSRFToken !== false && isURLSameOrigin(fullPath))) {
|
|
1468
1499
|
// Add xsrf header
|
|
1469
1500
|
var xsrfValue = config.xsrfHeaderName && config.xsrfCookieName && cookies.read(config.xsrfCookieName);
|
|
1470
1501
|
if (xsrfValue) {
|
|
@@ -1622,17 +1653,20 @@
|
|
|
1622
1653
|
var isFileList;
|
|
1623
1654
|
|
|
1624
1655
|
if (isObjectPayload) {
|
|
1656
|
+
var formSerializer = utils.hasOwnProperty(this, 'formSerializer') ? this.formSerializer : undefined;
|
|
1657
|
+
var envOption = utils.hasOwnProperty(this, 'env') ? this.env : undefined;
|
|
1658
|
+
|
|
1625
1659
|
if (contentType.indexOf('application/x-www-form-urlencoded') !== -1) {
|
|
1626
|
-
return toURLEncodedForm(data,
|
|
1660
|
+
return toURLEncodedForm(data, formSerializer).toString();
|
|
1627
1661
|
}
|
|
1628
1662
|
|
|
1629
1663
|
if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) {
|
|
1630
|
-
var _FormData =
|
|
1664
|
+
var _FormData = envOption && envOption.FormData;
|
|
1631
1665
|
|
|
1632
1666
|
return toFormData_1(
|
|
1633
1667
|
isFileList ? {'files[]': data} : data,
|
|
1634
1668
|
_FormData && new _FormData(),
|
|
1635
|
-
|
|
1669
|
+
formSerializer
|
|
1636
1670
|
);
|
|
1637
1671
|
}
|
|
1638
1672
|
}
|
|
@@ -1730,6 +1764,25 @@
|
|
|
1730
1764
|
return !!(value && value.__CANCEL__);
|
|
1731
1765
|
};
|
|
1732
1766
|
|
|
1767
|
+
var INVALID_HEADER_VALUE_RE = /[^\x09\x20-\x7E\x80-\xFF]/g;
|
|
1768
|
+
var BOUNDARY_WHITESPACE_RE = /^[\x09\x20]+|[\x09\x20]+$/g;
|
|
1769
|
+
|
|
1770
|
+
function sanitizeHeaderValue(value) {
|
|
1771
|
+
if (value === false || value == null) {
|
|
1772
|
+
return value;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
if (utils.isArray(value)) {
|
|
1776
|
+
return value.map(sanitizeHeaderValue);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
return String(value)
|
|
1780
|
+
.replace(INVALID_HEADER_VALUE_RE, '')
|
|
1781
|
+
.replace(BOUNDARY_WHITESPACE_RE, '');
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
var sanitizeHeaderValue_1 = sanitizeHeaderValue;
|
|
1785
|
+
|
|
1733
1786
|
/**
|
|
1734
1787
|
* Throws a `CanceledError` if cancellation has been requested.
|
|
1735
1788
|
*/
|
|
@@ -1781,6 +1834,10 @@
|
|
|
1781
1834
|
}
|
|
1782
1835
|
);
|
|
1783
1836
|
|
|
1837
|
+
utils.forEach(config.headers, function sanitizeHeaderConfigValue(value, header) {
|
|
1838
|
+
config.headers[header] = sanitizeHeaderValue_1(value);
|
|
1839
|
+
});
|
|
1840
|
+
|
|
1784
1841
|
var adapter = config.adapter || defaults_1.adapter;
|
|
1785
1842
|
|
|
1786
1843
|
return adapter(config).then(function onAdapterResolution(response) {
|
|
@@ -1827,7 +1884,17 @@
|
|
|
1827
1884
|
var mergeConfig = function mergeConfig(config1, config2) {
|
|
1828
1885
|
// eslint-disable-next-line no-param-reassign
|
|
1829
1886
|
config2 = config2 || {};
|
|
1830
|
-
|
|
1887
|
+
// Use a null-prototype object so a polluted Object.prototype cannot leak
|
|
1888
|
+
// values (e.g. transport, adapter) into the returned config via inheritance.
|
|
1889
|
+
var config = Object.create(null);
|
|
1890
|
+
|
|
1891
|
+
function getOwn(source, prop) {
|
|
1892
|
+
return utils.hasOwnProperty(source, prop) ? source[prop] : undefined;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
function hasOwn(source, prop) {
|
|
1896
|
+
return utils.hasOwnProperty(source, prop);
|
|
1897
|
+
}
|
|
1831
1898
|
|
|
1832
1899
|
function getMergedValue(target, source) {
|
|
1833
1900
|
if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
|
|
@@ -1844,34 +1911,34 @@
|
|
|
1844
1911
|
|
|
1845
1912
|
// eslint-disable-next-line consistent-return
|
|
1846
1913
|
function mergeDeepProperties(prop) {
|
|
1847
|
-
if (!utils.isUndefined(config2[prop])) {
|
|
1848
|
-
return getMergedValue(config1
|
|
1849
|
-
} else if (!utils.isUndefined(config1[prop])) {
|
|
1914
|
+
if (hasOwn(config2, prop) && !utils.isUndefined(config2[prop])) {
|
|
1915
|
+
return getMergedValue(getOwn(config1, prop), config2[prop]);
|
|
1916
|
+
} else if (hasOwn(config1, prop) && !utils.isUndefined(config1[prop])) {
|
|
1850
1917
|
return getMergedValue(undefined, config1[prop]);
|
|
1851
1918
|
}
|
|
1852
1919
|
}
|
|
1853
1920
|
|
|
1854
1921
|
// eslint-disable-next-line consistent-return
|
|
1855
1922
|
function valueFromConfig2(prop) {
|
|
1856
|
-
if (!utils.isUndefined(config2[prop])) {
|
|
1923
|
+
if (hasOwn(config2, prop) && !utils.isUndefined(config2[prop])) {
|
|
1857
1924
|
return getMergedValue(undefined, config2[prop]);
|
|
1858
1925
|
}
|
|
1859
1926
|
}
|
|
1860
1927
|
|
|
1861
1928
|
// eslint-disable-next-line consistent-return
|
|
1862
1929
|
function defaultToConfig2(prop) {
|
|
1863
|
-
if (!utils.isUndefined(config2[prop])) {
|
|
1930
|
+
if (hasOwn(config2, prop) && !utils.isUndefined(config2[prop])) {
|
|
1864
1931
|
return getMergedValue(undefined, config2[prop]);
|
|
1865
|
-
} else if (!utils.isUndefined(config1[prop])) {
|
|
1932
|
+
} else if (hasOwn(config1, prop) && !utils.isUndefined(config1[prop])) {
|
|
1866
1933
|
return getMergedValue(undefined, config1[prop]);
|
|
1867
1934
|
}
|
|
1868
1935
|
}
|
|
1869
1936
|
|
|
1870
1937
|
// eslint-disable-next-line consistent-return
|
|
1871
1938
|
function mergeDirectKeys(prop) {
|
|
1872
|
-
if (prop
|
|
1873
|
-
return getMergedValue(config1
|
|
1874
|
-
} else if (prop
|
|
1939
|
+
if (hasOwn(config2, prop)) {
|
|
1940
|
+
return getMergedValue(getOwn(config1, prop), config2[prop]);
|
|
1941
|
+
} else if (hasOwn(config1, prop)) {
|
|
1875
1942
|
return getMergedValue(undefined, config1[prop]);
|
|
1876
1943
|
}
|
|
1877
1944
|
}
|
|
@@ -1920,7 +1987,7 @@
|
|
|
1920
1987
|
};
|
|
1921
1988
|
|
|
1922
1989
|
var data = {
|
|
1923
|
-
version: "0.
|
|
1990
|
+
"version": "0.31.1"
|
|
1924
1991
|
};
|
|
1925
1992
|
|
|
1926
1993
|
var VERSION = data.version;
|