parse
Version:
Parse JavaScript SDK
342 lines (338 loc) • 12.6 kB
JavaScript
;
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
_Object$defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _endsWith = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/ends-with"));
var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice"));
var _startsWith = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/starts-with"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each"));
var _setTimeout2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set-timeout"));
var _uuid = _interopRequireDefault(require("./uuid"));
var _CoreManager = _interopRequireDefault(require("./CoreManager"));
var _ParseError = _interopRequireDefault(require("./ParseError"));
var _promiseUtils = require("./promiseUtils");
var _Xhr = require("./Xhr.weapp");
/* global XMLHttpRequest, XDomainRequest */
(0, _Xhr.polyfillFetch)();
let useXDomainRequest = false;
// @ts-ignore
if (typeof XDomainRequest !== 'undefined' && !('withCredentials' in new XMLHttpRequest())) {
useXDomainRequest = true;
}
function getPath(base, pathname) {
if ((0, _endsWith.default)(base).call(base, '/')) {
base = (0, _slice.default)(base).call(base, 0, -1);
}
if (!(0, _startsWith.default)(pathname).call(pathname, '/')) {
pathname = '/' + pathname;
}
return base + pathname;
}
function ajaxIE9(method, url, data, _headers, options) {
return new _promise.default((resolve, reject) => {
// @ts-ignore
const xdr = new XDomainRequest();
xdr.onload = function () {
let response;
try {
response = JSON.parse(xdr.responseText);
} catch (e) {
reject(e);
}
if (response) {
resolve({
response
});
}
};
xdr.onerror = xdr.ontimeout = function () {
// Let's fake a real error message.
const fakeResponse = {
responseText: (0, _stringify.default)({
code: _ParseError.default.X_DOMAIN_REQUEST,
error: "IE's XDomainRequest does not supply error info."
})
};
reject(fakeResponse);
};
xdr.onprogress = function () {
if (options && typeof options.progress === 'function') {
options.progress(xdr.responseText);
}
};
xdr.open(method, url);
xdr.send(data);
// @ts-ignore
if (options && typeof options.requestTask === 'function') {
// @ts-ignore
options.requestTask(xdr);
}
});
}
const RESTController = {
async ajax(method, url, data, headers, options) {
var _context;
if (useXDomainRequest) {
return ajaxIE9(method, url, data, headers, options);
}
if (typeof fetch !== 'function') {
throw new Error('Cannot make a request: Fetch API not found.');
}
const promise = (0, _promiseUtils.resolvingPromise)();
const isIdempotent = _CoreManager.default.get('IDEMPOTENCY') && (0, _includes.default)(_context = ['POST', 'PUT']).call(_context, method);
const requestId = isIdempotent ? (0, _uuid.default)() : '';
let attempts = 0;
const dispatch = async function () {
const controller = new AbortController();
const {
signal
} = controller;
headers = headers || {};
if (typeof headers['Content-Type'] !== 'string') {
headers['Content-Type'] = 'text/plain'; // Avoid pre-flight
}
if (_CoreManager.default.get('IS_NODE')) {
headers['User-Agent'] = 'Parse/' + _CoreManager.default.get('VERSION') + ' (NodeJS ' + process.versions.node + ')';
}
if (isIdempotent) {
headers['X-Parse-Request-Id'] = requestId;
}
const customHeaders = _CoreManager.default.get('REQUEST_HEADERS');
for (const key in customHeaders) {
headers[key] = customHeaders[key];
}
// @ts-ignore
if (options && typeof options.requestTask === 'function') {
// @ts-ignore
options.requestTask(controller);
}
try {
var _context3;
const fetchOptions = {
method,
headers,
signal,
redirect: 'manual'
};
if (data) {
fetchOptions.body = data;
}
const response = await fetch(url, fetchOptions);
const {
status
} = response;
if (status >= 200 && status < 300) {
var _context2;
let result;
const responseHeaders = {};
const availableHeaders = response.headers.get('access-control-expose-headers') || '';
(0, _forEach.default)(_context2 = availableHeaders.split(', ')).call(_context2, header => {
if (header && response.headers.has(header)) {
responseHeaders[header] = response.headers.get(header);
}
});
if (options && typeof options.progress === 'function' && response.body) {
const reader = response.body.getReader();
const length = +response.headers.get('Content-Length') || 0;
if (length === 0) {
options.progress(null, null, null);
result = await response.json();
} else {
let recieved = 0;
const chunks = [];
while (true) {
const {
done,
value
} = await reader.read();
if (done) {
break;
}
chunks.push(value);
recieved += value?.length || 0;
options.progress(recieved / length, recieved, length);
}
const body = new Uint8Array(recieved);
let offset = 0;
for (const chunk of chunks) {
body.set(chunk, offset);
offset += chunk.length;
}
const jsonString = new TextDecoder().decode(body);
result = JSON.parse(jsonString);
}
} else {
result = await response.json();
}
promise.resolve({
status,
response: result,
headers: responseHeaders
});
} else if (status >= 400 && status < 500) {
const error = await response.json();
promise.reject(error);
} else if ((0, _includes.default)(_context3 = [301, 302, 303, 307, 308]).call(_context3, status)) {
const location = response.headers.get('location');
promise.resolve({
status,
location,
method: status === 303 ? 'GET' : method,
dropBody: status === 303
});
} else if (status >= 500 || status === 0) {
// retry on 5XX or library error
if (++attempts < _CoreManager.default.get('REQUEST_ATTEMPT_LIMIT')) {
// Exponentially-growing random delay
const delay = Math.round(Math.random() * 125 * Math.pow(2, attempts));
(0, _setTimeout2.default)(dispatch, delay);
} else if (status === 0) {
promise.reject('Unable to connect to the Parse API');
} else {
// After the retry limit is reached, fail
const error = await response.json();
promise.reject(error);
}
} else {
promise.reject(response);
}
} catch (error) {
if (error.name === 'AbortError') {
promise.resolve({
response: {
results: []
},
status: 0
});
} else if (error.cause?.code === 'ECONNREFUSED') {
promise.reject('Unable to connect to the Parse API');
} else {
promise.reject(error);
}
}
};
dispatch();
return promise;
},
request(method, path, data, options) {
options = options || {};
const url = getPath(_CoreManager.default.get('SERVER_URL'), path);
const payload = {};
if (data && typeof data === 'object') {
for (const k in data) {
payload[k] = data[k];
}
}
// Add context
const context = options.context;
if (context !== undefined) {
payload._context = context;
}
if (method !== 'POST') {
payload._method = method;
method = 'POST';
}
payload._ApplicationId = _CoreManager.default.get('APPLICATION_ID');
const jsKey = _CoreManager.default.get('JAVASCRIPT_KEY');
if (jsKey) {
payload._JavaScriptKey = jsKey;
}
payload._ClientVersion = _CoreManager.default.get('VERSION');
let useMasterKey = options.useMasterKey;
if (typeof useMasterKey === 'undefined') {
useMasterKey = _CoreManager.default.get('USE_MASTER_KEY');
}
if (useMasterKey) {
if (_CoreManager.default.get('MASTER_KEY')) {
delete payload._JavaScriptKey;
payload._MasterKey = _CoreManager.default.get('MASTER_KEY');
} else {
throw new Error('Cannot use the Master Key, it has not been provided.');
}
}
if (options.useMaintenanceKey) {
payload._MaintenanceKey = _CoreManager.default.get('MAINTENANCE_KEY');
}
if (_CoreManager.default.get('FORCE_REVOCABLE_SESSION')) {
payload._RevocableSession = '1';
}
const installationId = options.installationId;
let installationIdPromise;
if (installationId && typeof installationId === 'string') {
installationIdPromise = _promise.default.resolve(installationId);
} else {
const installationController = _CoreManager.default.getInstallationController();
installationIdPromise = installationController.currentInstallationId();
}
return installationIdPromise.then(iid => {
payload._InstallationId = iid;
const userController = _CoreManager.default.getUserController();
if (options && typeof options.sessionToken === 'string') {
return _promise.default.resolve(options.sessionToken);
} else if (userController) {
return userController.currentUserAsync().then(user => {
if (user) {
return _promise.default.resolve(user.getSessionToken());
}
return _promise.default.resolve(null);
});
}
return _promise.default.resolve(null);
}).then(token => {
if (token) {
payload._SessionToken = token;
}
const payloadString = (0, _stringify.default)(payload);
return RESTController.ajax(method, url, payloadString, {}, options).then(async result => {
if (result.location) {
let newURL = getPath(result.location, path);
let newMethod = result.method;
let newBody = result.dropBody ? undefined : payloadString;
// Follow up to 5 redirects to avoid loops
for (let i = 0; i < 5; i += 1) {
const r = await RESTController.ajax(newMethod, newURL, newBody, {}, options);
if (!r.location) {
result = r;
break;
}
newURL = getPath(r.location, path);
newMethod = r.method;
newBody = r.dropBody ? undefined : payloadString;
}
}
const {
response,
status,
headers
} = result;
if (options.returnStatus) {
return {
...response,
_status: status,
_headers: headers
};
} else {
return response;
}
});
}).catch(RESTController.handleError);
},
handleError(errorJSON) {
// Transform the error into an instance of ParseError by trying to parse
// the error string as JSON
let error;
if (errorJSON.code || errorJSON.error || errorJSON.message) {
error = new _ParseError.default(errorJSON.code, errorJSON.error || errorJSON.message);
} else {
error = new _ParseError.default(_ParseError.default.CONNECTION_FAILED, 'XMLHttpRequest failed: ' + (0, _stringify.default)(errorJSON));
}
return _promise.default.reject(error);
}
};
var _default = exports.default = RESTController;