@gooddata/gooddata-js
Version:
GoodData JavaScript SDK
379 lines (378 loc) • 18.1 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
// (C) 2007-2020 GoodData Corporation
var isPlainObject_1 = __importDefault(require("lodash/isPlainObject"));
var isFunction_1 = __importDefault(require("lodash/isFunction"));
var set_1 = __importDefault(require("lodash/set"));
var defaults_1 = __importDefault(require("lodash/defaults"));
var merge_1 = __importDefault(require("lodash/merge"));
var result_1 = __importDefault(require("lodash/result"));
var util_1 = require("./util");
/**
* Ajax wrapper around GDC authentication mechanisms, SST and TT token handling and polling.
* Interface is the same as original jQuery.ajax.
*
* If token is expired, current request is "paused", token is refreshed and request is retried and result
* is transparently returned to the original call.
*
* Additionally polling is handled. Only final result of polling returned.
* @module xhr
* @class xhr
*/
var DEFAULT_POLL_DELAY = 1000;
var REST_API_VERSION_HEADER = "X-GDC-VERSION";
var REST_API_DEPRECATED_VERSION_HEADER = "X-GDC-DEPRECATED";
// The version used in X-GDC-VERSION header (see https://confluence.intgdc.com/display/Development/REST+API+versioning)
var LATEST_REST_API_VERSION = 5;
function simulateBeforeSend(url, settings) {
var xhrMockInBeforeSend = {
setRequestHeader: function (key, value) {
set_1.default(settings, ["headers", key], value);
},
};
if (isFunction_1.default(settings.beforeSend)) {
settings.beforeSend(xhrMockInBeforeSend, url);
}
}
function enrichSettingWithCustomDomain(originalUrl, originalSettings, domain) {
var url = originalUrl;
var settings = originalSettings;
if (domain) {
// protect url to be prepended with domain on retry
if (originalUrl.indexOf(domain) === -1) {
url = domain + originalUrl;
}
settings.mode = "cors";
settings.credentials = "include";
}
return { url: url, settings: settings };
}
function handlePolling(url, settings, sendRequest) {
var pollingDelay = result_1.default(settings, "pollDelay");
return new Promise(function (resolve, reject) {
setTimeout(function () {
sendRequest(url, settings).then(resolve, reject);
}, pollingDelay);
});
}
exports.handlePolling = handlePolling;
function originPackageHeaders(_a) {
var name = _a.name, version = _a.version;
return {
"X-GDC-JS-PKG": name,
"X-GDC-JS-PKG-VERSION": version,
};
}
exports.originPackageHeaders = originPackageHeaders;
var ApiError = /** @class */ (function (_super) {
__extends(ApiError, _super);
function ApiError(message, cause) {
var _this = _super.call(this, message) || this;
_this.cause = cause;
return _this;
}
return ApiError;
}(Error));
exports.ApiError = ApiError;
var ApiResponseError = /** @class */ (function (_super) {
__extends(ApiResponseError, _super);
function ApiResponseError(message, response, responseBody) {
var _this = _super.call(this, message, null) || this;
_this.response = response;
_this.responseBody = responseBody;
return _this;
}
return ApiResponseError;
}(ApiError));
exports.ApiResponseError = ApiResponseError;
var ApiNetworkError = /** @class */ (function (_super) {
__extends(ApiNetworkError, _super);
function ApiNetworkError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ApiNetworkError;
}(ApiError));
exports.ApiNetworkError = ApiNetworkError;
var ApiResponse = /** @class */ (function () {
function ApiResponse(response, responseBody) {
this.response = response;
this.responseBody = responseBody;
}
Object.defineProperty(ApiResponse.prototype, "data", {
get: function () {
try {
return JSON.parse(this.responseBody);
}
catch (error) {
return this.responseBody;
}
},
enumerable: true,
configurable: true
});
ApiResponse.prototype.getData = function () {
try {
return JSON.parse(this.responseBody);
}
catch (error) {
return this.responseBody;
}
};
ApiResponse.prototype.getHeaders = function () {
return this.response;
};
return ApiResponse;
}());
exports.ApiResponse = ApiResponse;
// the variable must be outside of the scope of the XhrModule to not log the message multiple times in SDK and KD
var shouldLogDeprecatedRestApiCall = true;
var XhrModule = /** @class */ (function () {
function XhrModule(fetch, configStorage) {
this.fetch = fetch;
this.configStorage = configStorage;
defaults_1.default(configStorage, { xhrSettings: {} });
}
/**
* Back compatible method for setting common XHR settings
*
* Usually in our apps we used beforeSend ajax callback to set the X-GDC-REQUEST header with unique ID.
*
* @param settings object XHR settings as
*/
XhrModule.prototype.ajaxSetup = function (settings) {
Object.assign(this.configStorage.xhrSettings, settings);
};
XhrModule.prototype.ajax = function (originalUrl, customSettings) {
if (customSettings === void 0) { customSettings = {}; }
return __awaiter(this, void 0, void 0, function () {
var firstSettings, _a, url, settings, response, e_1, responseBody, finalUrl, finalSettings;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
firstSettings = this.createRequestSettings(customSettings);
_a = enrichSettingWithCustomDomain(originalUrl, firstSettings, this.configStorage.domain), url = _a.url, settings = _a.settings;
simulateBeforeSend(url, settings); // mutates `settings` param
if (this.tokenRequest) {
return [2 /*return*/, this.continueAfterTokenRequest(url, settings)];
}
_b.label = 1;
case 1:
_b.trys.push([1, 3, , 4]);
return [4 /*yield*/, this.fetch(url, settings)];
case 2:
// TODO: We should clean up the settings at this point to be pure `RequestInit` object
response = _b.sent();
return [3 /*break*/, 4];
case 3:
e_1 = _b.sent();
throw new ApiNetworkError(e_1.message, e_1); // TODO is it really necessary? couldn't we throw just Error?
case 4: return [4 /*yield*/, response.text()];
case 5:
responseBody = _b.sent();
if (response.status === 401) {
// if 401 is in login-request, it means wrong user/password (we wont continue)
if (url.indexOf("/gdc/account/login") !== -1) {
throw new ApiResponseError("Unauthorized", response, responseBody);
}
return [2 /*return*/, this.handleUnauthorized(url, settings)];
}
// Note: Fetch does redirects automagically for 301 (and maybe more .. TODO when?)
// see https://fetch.spec.whatwg.org/#ref-for-concept-request%E2%91%A3%E2%91%A2
if (response.status === 202 && !settings.dontPollOnResult) {
finalUrl = response.url || url;
finalSettings = settings;
// if the response is 202 and Location header is not empty, let's poll on the new Location
if (response.headers.has("Location")) {
finalUrl = response.headers.get("Location");
}
finalSettings.method = "GET";
delete finalSettings.data;
delete finalSettings.body;
return [2 /*return*/, handlePolling(finalUrl, finalSettings, this.ajax.bind(this))];
}
this.verifyRestApiDeprecationStatus(response.headers);
if (response.status >= 200 && response.status <= 399) {
return [2 /*return*/, new ApiResponse(response, responseBody)];
}
// throws on 400, 500, etc.
throw new ApiResponseError(response.statusText, response, responseBody);
}
});
});
};
/**
* Wrapper for xhr.ajax method GET
*/
XhrModule.prototype.get = function (url, settings) {
return this.ajax(url, merge_1.default({ method: "GET" }, settings));
};
/**
* Wrapper for xhr.ajax method HEAD
*/
XhrModule.prototype.head = function (url, settings) {
return this.ajax(url, merge_1.default({ method: "HEAD" }, settings));
};
/**
* Wrapper for xhr.ajax method POST
*/
XhrModule.prototype.post = function (url, settings) {
return this.ajax(url, merge_1.default({ method: "POST" }, settings));
};
/**
* Wrapper for xhr.ajax method PUT
*/
XhrModule.prototype.put = function (url, settings) {
return this.ajax(url, merge_1.default({ method: "PUT" }, settings));
};
/**
* Wrapper for xhr.ajax method DELETE
*/
XhrModule.prototype.del = function (url, settings) {
return this.ajax(url, merge_1.default({ method: "DELETE" }, settings));
};
XhrModule.prototype.createRequestSettings = function (customSettings) {
var _a;
var settings = merge_1.default({
headers: __assign((_a = { Accept: "application/json; charset=utf-8", "Content-Type": "application/json" }, _a[REST_API_VERSION_HEADER] = LATEST_REST_API_VERSION, _a), originPackageHeaders(this.configStorage.originPackage || util_1.thisPackage)),
}, this.configStorage.xhrSettings, customSettings);
settings.pollDelay = settings.pollDelay !== undefined ? settings.pollDelay : DEFAULT_POLL_DELAY;
// TODO jquery compat - add to warnings
settings.body = settings.data ? settings.data : settings.body;
settings.mode = "same-origin";
settings.credentials = "same-origin";
if (isPlainObject_1.default(settings.body)) {
settings.body = JSON.stringify(settings.body);
}
return settings;
};
XhrModule.prototype.continueAfterTokenRequest = function (url, settings) {
var _this = this;
return this.tokenRequest.then(function (response) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (!response.ok) {
throw new ApiResponseError("Unauthorized", response, null);
}
this.tokenRequest = null;
return [2 /*return*/, this.ajax(url, settings)];
});
}); }, function (reason) {
_this.tokenRequest = null;
return reason;
});
};
XhrModule.prototype.handleUnauthorized = function (originalUrl, originalSettings) {
return __awaiter(this, void 0, void 0, function () {
var _a, url, settings, response, responseBody;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
// Create only single token request for any number of waiting request.
// If token request exist, just listen for it's end.
if (this.tokenRequest) {
return [2 /*return*/, this.continueAfterTokenRequest(originalUrl, originalSettings)];
}
_a = enrichSettingWithCustomDomain("/gdc/account/token", this.createRequestSettings({}), this.configStorage.domain), url = _a.url, settings = _a.settings;
this.tokenRequest = this.fetch(url, settings);
return [4 /*yield*/, this.tokenRequest];
case 1:
response = _b.sent();
return [4 /*yield*/, response.text()];
case 2:
responseBody = _b.sent();
this.tokenRequest = null;
// TODO jquery compat - allow to attach unauthorized callback and call it if attached
// if ((xhrObj.status === 401) && (isFunction(req.unauthorized))) {
// req.unauthorized(xhrObj, textStatus, err, deferred);
// return;
// }
// unauthorized handler is not defined or not http 401
// unauthorized when retrieving token -> not logged
if (response.status === 401) {
throw new ApiResponseError("Unauthorized", response, responseBody);
}
return [2 /*return*/, this.ajax(originalUrl, originalSettings)];
}
});
});
};
XhrModule.prototype.logDeprecatedRestApiCall = function (deprecatedVersionDetails) {
// tslint:disable-next-line:no-console
console.warn("The REST API version " + LATEST_REST_API_VERSION + " is deprecated (" + deprecatedVersionDetails + "). " +
"Please migrate your application to use GoodData.UI SDK or @gooddata/gooddata-js package that " +
"supports newer version of the API.");
};
XhrModule.prototype.isRestApiDeprecated = function (responseHeaders) {
return responseHeaders.has(REST_API_DEPRECATED_VERSION_HEADER);
};
XhrModule.prototype.verifyRestApiDeprecationStatus = function (responseHeaders) {
if (shouldLogDeprecatedRestApiCall && this.isRestApiDeprecated(responseHeaders)) {
var deprecatedVersionDetails = responseHeaders.get(REST_API_DEPRECATED_VERSION_HEADER);
this.logDeprecatedRestApiCall(deprecatedVersionDetails);
shouldLogDeprecatedRestApiCall = false;
}
};
return XhrModule;
}());
exports.XhrModule = XhrModule;