UNPKG

18.1 kBJavaScriptView Raw
1"use strict";
2var __extends = (this && this.__extends) || (function () {
3 var extendStatics = function (d, b) {
4 extendStatics = Object.setPrototypeOf ||
5 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
7 return extendStatics(d, b);
8 };
9 return function (d, b) {
10 extendStatics(d, b);
11 function __() { this.constructor = d; }
12 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
13 };
14})();
15var __assign = (this && this.__assign) || function () {
16 __assign = Object.assign || function(t) {
17 for (var s, i = 1, n = arguments.length; i < n; i++) {
18 s = arguments[i];
19 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
20 t[p] = s[p];
21 }
22 return t;
23 };
24 return __assign.apply(this, arguments);
25};
26var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
27 return new (P || (P = Promise))(function (resolve, reject) {
28 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
31 step((generator = generator.apply(thisArg, _arguments || [])).next());
32 });
33};
34var __generator = (this && this.__generator) || function (thisArg, body) {
35 var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
36 return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
37 function verb(n) { return function (v) { return step([n, v]); }; }
38 function step(op) {
39 if (f) throw new TypeError("Generator is already executing.");
40 while (_) try {
41 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;
42 if (y = 0, t) op = [op[0] & 2, t.value];
43 switch (op[0]) {
44 case 0: case 1: t = op; break;
45 case 4: _.label++; return { value: op[1], done: false };
46 case 5: _.label++; y = op[1]; op = [0]; continue;
47 case 7: op = _.ops.pop(); _.trys.pop(); continue;
48 default:
49 if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
50 if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
51 if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
52 if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
53 if (t[2]) _.ops.pop();
54 _.trys.pop(); continue;
55 }
56 op = body.call(thisArg, _);
57 } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
58 if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
59 }
60};
61var __importDefault = (this && this.__importDefault) || function (mod) {
62 return (mod && mod.__esModule) ? mod : { "default": mod };
63};
64Object.defineProperty(exports, "__esModule", { value: true });
65// (C) 2007-2020 GoodData Corporation
66var isPlainObject_1 = __importDefault(require("lodash/isPlainObject"));
67var isFunction_1 = __importDefault(require("lodash/isFunction"));
68var set_1 = __importDefault(require("lodash/set"));
69var defaults_1 = __importDefault(require("lodash/defaults"));
70var merge_1 = __importDefault(require("lodash/merge"));
71var result_1 = __importDefault(require("lodash/result"));
72var util_1 = require("./util");
73/**
74 * Ajax wrapper around GDC authentication mechanisms, SST and TT token handling and polling.
75 * Interface is the same as original jQuery.ajax.
76 *
77 * If token is expired, current request is "paused", token is refreshed and request is retried and result
78 * is transparently returned to the original call.
79 *
80 * Additionally polling is handled. Only final result of polling returned.
81 * @module xhr
82 * @class xhr
83 */
84var DEFAULT_POLL_DELAY = 1000;
85var REST_API_VERSION_HEADER = "X-GDC-VERSION";
86var REST_API_DEPRECATED_VERSION_HEADER = "X-GDC-DEPRECATED";
87// The version used in X-GDC-VERSION header (see https://confluence.intgdc.com/display/Development/REST+API+versioning)
88var LATEST_REST_API_VERSION = 5;
89function simulateBeforeSend(url, settings) {
90 var xhrMockInBeforeSend = {
91 setRequestHeader: function (key, value) {
92 set_1.default(settings, ["headers", key], value);
93 },
94 };
95 if (isFunction_1.default(settings.beforeSend)) {
96 settings.beforeSend(xhrMockInBeforeSend, url);
97 }
98}
99function enrichSettingWithCustomDomain(originalUrl, originalSettings, domain) {
100 var url = originalUrl;
101 var settings = originalSettings;
102 if (domain) {
103 // protect url to be prepended with domain on retry
104 if (originalUrl.indexOf(domain) === -1) {
105 url = domain + originalUrl;
106 }
107 settings.mode = "cors";
108 settings.credentials = "include";
109 }
110 return { url: url, settings: settings };
111}
112function handlePolling(url, settings, sendRequest) {
113 var pollingDelay = result_1.default(settings, "pollDelay");
114 return new Promise(function (resolve, reject) {
115 setTimeout(function () {
116 sendRequest(url, settings).then(resolve, reject);
117 }, pollingDelay);
118 });
119}
120exports.handlePolling = handlePolling;
121function originPackageHeaders(_a) {
122 var name = _a.name, version = _a.version;
123 return {
124 "X-GDC-JS-PKG": name,
125 "X-GDC-JS-PKG-VERSION": version,
126 };
127}
128exports.originPackageHeaders = originPackageHeaders;
129var ApiError = /** @class */ (function (_super) {
130 __extends(ApiError, _super);
131 function ApiError(message, cause) {
132 var _this = _super.call(this, message) || this;
133 _this.cause = cause;
134 return _this;
135 }
136 return ApiError;
137}(Error));
138exports.ApiError = ApiError;
139var ApiResponseError = /** @class */ (function (_super) {
140 __extends(ApiResponseError, _super);
141 function ApiResponseError(message, response, responseBody) {
142 var _this = _super.call(this, message, null) || this;
143 _this.response = response;
144 _this.responseBody = responseBody;
145 return _this;
146 }
147 return ApiResponseError;
148}(ApiError));
149exports.ApiResponseError = ApiResponseError;
150var ApiNetworkError = /** @class */ (function (_super) {
151 __extends(ApiNetworkError, _super);
152 function ApiNetworkError() {
153 return _super !== null && _super.apply(this, arguments) || this;
154 }
155 return ApiNetworkError;
156}(ApiError));
157exports.ApiNetworkError = ApiNetworkError;
158var ApiResponse = /** @class */ (function () {
159 function ApiResponse(response, responseBody) {
160 this.response = response;
161 this.responseBody = responseBody;
162 }
163 Object.defineProperty(ApiResponse.prototype, "data", {
164 get: function () {
165 try {
166 return JSON.parse(this.responseBody);
167 }
168 catch (error) {
169 return this.responseBody;
170 }
171 },
172 enumerable: true,
173 configurable: true
174 });
175 ApiResponse.prototype.getData = function () {
176 try {
177 return JSON.parse(this.responseBody);
178 }
179 catch (error) {
180 return this.responseBody;
181 }
182 };
183 ApiResponse.prototype.getHeaders = function () {
184 return this.response;
185 };
186 return ApiResponse;
187}());
188exports.ApiResponse = ApiResponse;
189// the variable must be outside of the scope of the XhrModule to not log the message multiple times in SDK and KD
190var shouldLogDeprecatedRestApiCall = true;
191var XhrModule = /** @class */ (function () {
192 function XhrModule(fetch, configStorage) {
193 this.fetch = fetch;
194 this.configStorage = configStorage;
195 defaults_1.default(configStorage, { xhrSettings: {} });
196 }
197 /**
198 * Back compatible method for setting common XHR settings
199 *
200 * Usually in our apps we used beforeSend ajax callback to set the X-GDC-REQUEST header with unique ID.
201 *
202 * @param settings object XHR settings as
203 */
204 XhrModule.prototype.ajaxSetup = function (settings) {
205 Object.assign(this.configStorage.xhrSettings, settings);
206 };
207 XhrModule.prototype.ajax = function (originalUrl, customSettings) {
208 if (customSettings === void 0) { customSettings = {}; }
209 return __awaiter(this, void 0, void 0, function () {
210 var firstSettings, _a, url, settings, response, e_1, responseBody, finalUrl, finalSettings;
211 return __generator(this, function (_b) {
212 switch (_b.label) {
213 case 0:
214 firstSettings = this.createRequestSettings(customSettings);
215 _a = enrichSettingWithCustomDomain(originalUrl, firstSettings, this.configStorage.domain), url = _a.url, settings = _a.settings;
216 simulateBeforeSend(url, settings); // mutates `settings` param
217 if (this.tokenRequest) {
218 return [2 /*return*/, this.continueAfterTokenRequest(url, settings)];
219 }
220 _b.label = 1;
221 case 1:
222 _b.trys.push([1, 3, , 4]);
223 return [4 /*yield*/, this.fetch(url, settings)];
224 case 2:
225 // TODO: We should clean up the settings at this point to be pure `RequestInit` object
226 response = _b.sent();
227 return [3 /*break*/, 4];
228 case 3:
229 e_1 = _b.sent();
230 throw new ApiNetworkError(e_1.message, e_1); // TODO is it really necessary? couldn't we throw just Error?
231 case 4: return [4 /*yield*/, response.text()];
232 case 5:
233 responseBody = _b.sent();
234 if (response.status === 401) {
235 // if 401 is in login-request, it means wrong user/password (we wont continue)
236 if (url.indexOf("/gdc/account/login") !== -1) {
237 throw new ApiResponseError("Unauthorized", response, responseBody);
238 }
239 return [2 /*return*/, this.handleUnauthorized(url, settings)];
240 }
241 // Note: Fetch does redirects automagically for 301 (and maybe more .. TODO when?)
242 // see https://fetch.spec.whatwg.org/#ref-for-concept-request%E2%91%A3%E2%91%A2
243 if (response.status === 202 && !settings.dontPollOnResult) {
244 finalUrl = response.url || url;
245 finalSettings = settings;
246 // if the response is 202 and Location header is not empty, let's poll on the new Location
247 if (response.headers.has("Location")) {
248 finalUrl = response.headers.get("Location");
249 }
250 finalSettings.method = "GET";
251 delete finalSettings.data;
252 delete finalSettings.body;
253 return [2 /*return*/, handlePolling(finalUrl, finalSettings, this.ajax.bind(this))];
254 }
255 this.verifyRestApiDeprecationStatus(response.headers);
256 if (response.status >= 200 && response.status <= 399) {
257 return [2 /*return*/, new ApiResponse(response, responseBody)];
258 }
259 // throws on 400, 500, etc.
260 throw new ApiResponseError(response.statusText, response, responseBody);
261 }
262 });
263 });
264 };
265 /**
266 * Wrapper for xhr.ajax method GET
267 */
268 XhrModule.prototype.get = function (url, settings) {
269 return this.ajax(url, merge_1.default({ method: "GET" }, settings));
270 };
271 /**
272 * Wrapper for xhr.ajax method HEAD
273 */
274 XhrModule.prototype.head = function (url, settings) {
275 return this.ajax(url, merge_1.default({ method: "HEAD" }, settings));
276 };
277 /**
278 * Wrapper for xhr.ajax method POST
279 */
280 XhrModule.prototype.post = function (url, settings) {
281 return this.ajax(url, merge_1.default({ method: "POST" }, settings));
282 };
283 /**
284 * Wrapper for xhr.ajax method PUT
285 */
286 XhrModule.prototype.put = function (url, settings) {
287 return this.ajax(url, merge_1.default({ method: "PUT" }, settings));
288 };
289 /**
290 * Wrapper for xhr.ajax method DELETE
291 */
292 XhrModule.prototype.del = function (url, settings) {
293 return this.ajax(url, merge_1.default({ method: "DELETE" }, settings));
294 };
295 XhrModule.prototype.createRequestSettings = function (customSettings) {
296 var _a;
297 var settings = merge_1.default({
298 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)),
299 }, this.configStorage.xhrSettings, customSettings);
300 settings.pollDelay = settings.pollDelay !== undefined ? settings.pollDelay : DEFAULT_POLL_DELAY;
301 // TODO jquery compat - add to warnings
302 settings.body = settings.data ? settings.data : settings.body;
303 settings.mode = "same-origin";
304 settings.credentials = "same-origin";
305 if (isPlainObject_1.default(settings.body)) {
306 settings.body = JSON.stringify(settings.body);
307 }
308 return settings;
309 };
310 XhrModule.prototype.continueAfterTokenRequest = function (url, settings) {
311 var _this = this;
312 return this.tokenRequest.then(function (response) { return __awaiter(_this, void 0, void 0, function () {
313 return __generator(this, function (_a) {
314 if (!response.ok) {
315 throw new ApiResponseError("Unauthorized", response, null);
316 }
317 this.tokenRequest = null;
318 return [2 /*return*/, this.ajax(url, settings)];
319 });
320 }); }, function (reason) {
321 _this.tokenRequest = null;
322 return reason;
323 });
324 };
325 XhrModule.prototype.handleUnauthorized = function (originalUrl, originalSettings) {
326 return __awaiter(this, void 0, void 0, function () {
327 var _a, url, settings, response, responseBody;
328 return __generator(this, function (_b) {
329 switch (_b.label) {
330 case 0:
331 // Create only single token request for any number of waiting request.
332 // If token request exist, just listen for it's end.
333 if (this.tokenRequest) {
334 return [2 /*return*/, this.continueAfterTokenRequest(originalUrl, originalSettings)];
335 }
336 _a = enrichSettingWithCustomDomain("/gdc/account/token", this.createRequestSettings({}), this.configStorage.domain), url = _a.url, settings = _a.settings;
337 this.tokenRequest = this.fetch(url, settings);
338 return [4 /*yield*/, this.tokenRequest];
339 case 1:
340 response = _b.sent();
341 return [4 /*yield*/, response.text()];
342 case 2:
343 responseBody = _b.sent();
344 this.tokenRequest = null;
345 // TODO jquery compat - allow to attach unauthorized callback and call it if attached
346 // if ((xhrObj.status === 401) && (isFunction(req.unauthorized))) {
347 // req.unauthorized(xhrObj, textStatus, err, deferred);
348 // return;
349 // }
350 // unauthorized handler is not defined or not http 401
351 // unauthorized when retrieving token -> not logged
352 if (response.status === 401) {
353 throw new ApiResponseError("Unauthorized", response, responseBody);
354 }
355 return [2 /*return*/, this.ajax(originalUrl, originalSettings)];
356 }
357 });
358 });
359 };
360 XhrModule.prototype.logDeprecatedRestApiCall = function (deprecatedVersionDetails) {
361 // tslint:disable-next-line:no-console
362 console.warn("The REST API version " + LATEST_REST_API_VERSION + " is deprecated (" + deprecatedVersionDetails + "). " +
363 "Please migrate your application to use GoodData.UI SDK or @gooddata/gooddata-js package that " +
364 "supports newer version of the API.");
365 };
366 XhrModule.prototype.isRestApiDeprecated = function (responseHeaders) {
367 return responseHeaders.has(REST_API_DEPRECATED_VERSION_HEADER);
368 };
369 XhrModule.prototype.verifyRestApiDeprecationStatus = function (responseHeaders) {
370 if (shouldLogDeprecatedRestApiCall && this.isRestApiDeprecated(responseHeaders)) {
371 var deprecatedVersionDetails = responseHeaders.get(REST_API_DEPRECATED_VERSION_HEADER);
372 this.logDeprecatedRestApiCall(deprecatedVersionDetails);
373 shouldLogDeprecatedRestApiCall = false;
374 }
375 };
376 return XhrModule;
377}());
378exports.XhrModule = XhrModule;