UNPKG

@react-mvi/http

Version:

Http IO module for React MVI.

493 lines (492 loc) 23.9 kB
"use strict"; /** * The MIT License (MIT) * Copyright (c) Taketoshi Aono * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * @fileoverview * @author Taketoshi Aono */ 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 __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 }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var core_1 = require("@react-mvi/core"); var rxjs_1 = require("rxjs"); var http_response_1 = require("./http-response"); var qs_1 = require("./qs"); var types_1 = require("./types"); var DEFAULT_ERROR_STATUS = 500; /** * Http request sender. */ var HttpHandler = /** @class */ (function (_super) { __extends(HttpHandler, _super); function HttpHandler(a) { var _this = _super.call(this, a, { request: ['get', 'post', 'put', 'delete', 'upload', 'patch'], response: 'notifyResponse', uploading: 'notifyUploading', }) || this; _this.history = []; return _this; } Object.defineProperty(HttpHandler, "maxHistoryLength", { set: function (length) { this._maxHistoryLength = length; }, enumerable: true, configurable: true }); Object.defineProperty(HttpHandler, "maxHistoryLenght", { get: function () { return this._maxHistoryLength; }, enumerable: true, configurable: true }); HttpHandler.prototype.clone = function () { return new HttpHandler(this.advices); }; /** * Wait for request from observables. * @override * @param request Observable that send request. */ HttpHandler.prototype.subscribe = function (props) { var _this = this; var subscription = new rxjs_1.Subscription(); if (props.http) { if (props.http instanceof rxjs_1.Observable) { subscription.add(props.http.subscribe(function (args) { if (Array.isArray(args)) { args.forEach(function (_a) { var type = _a.type, request = _a.request; return _this.push(type, request); }); } else { _this.push(args.type, args.request); } }, function (error) { return console.error(error); })); } else { var _loop_1 = function (reqKey) { var req = props.http[reqKey]; subscription.add(req.subscribe(function (config) { return _this.push(reqKey, config); }, function (error) { return console.error(error); })); }; for (var reqKey in props.http) { _loop_1(reqKey); } for (var reqKey in props.http) { var req = props.http[reqKey]; if (req instanceof rxjs_1.ConnectableObservable && req['connect']) { req.connect(); } } } } return subscription; }; /** * @inheritDoc */ HttpHandler.prototype.push = function (key, args) { return __awaiter(this, void 0, void 0, function () { var history_1, config, subjectsOK, subjectsNG, subjectsProgress, errorHandler, succeededHandler; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (key === 'RETRY') { history_1 = this.history[this.history.length - (typeof args === 'number' ? args + 1 : 1)]; if (!history_1) { return [2 /*return*/, new Promise(function (_, r) { return r(new Error('Invlaid retry number specified.')); })]; } key = history_1.key; args = history_1.args; } else { if (this.history.length > HttpHandler._maxHistoryLength) { this.history.shift(); } this.history.push({ key: key, args: args }); } if (!args) { return [2 /*return*/, new Promise(function (_, r) { return r(new Error('Config required.')); })]; } config = args; subjectsOK = this.store.get(key).concat(this.store.get(key + "-ok")); subjectsNG = this.store.get(key).concat(this.store.get(key + "-ng")); subjectsProgress = this.store .get(key) .concat(this.store.get(key + "-uploading")); errorHandler = function (config, err, result) { var httpResponse = new http_response_1.HttpResponseImpl(false, err && err.status ? err.status : DEFAULT_ERROR_STATUS, {}, null, result); var ret = config.reduce(httpResponse, _this.state); _this.notifyResponse(config, key + "-ng", httpResponse, ret, subjectsNG); }; succeededHandler = function (config, response, result) { var headers = _this.processHeaders(response); var httpResponse = new http_response_1.HttpResponseImpl(response.ok, response.status, headers, response.ok ? result : null, response.ok ? null : result); var ret = config.reduce(httpResponse, _this.state); _this.notifyResponse(config, key + "-ok", httpResponse, ret, subjectsOK); }; if (!config.reduce) { config.reduce = function (v) { return v; }; } if (config.upload) { return [2 /*return*/, this.upload(config, key).then(function (subject) { _this.handleUploadResonse(subjectsOK, subjectsNG, subjectsProgress, subject, config, key); })]; } return [4 /*yield*/, this.handleResponse(config, key, function (res, ret) { return succeededHandler(config, res, ret); }, function (e, ret) { return errorHandler(config, e, ret); })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; HttpHandler.prototype.handleUploadResonse = function (subjectsOK, subjectsNG, subjectsUploading, subject, config, key) { var _this = this; var sub = subject.subscribe(function (e) { if (e.type !== types_1.UploadEventType.PROGRESS) { sub.unsubscribe(); var isComplete = e.type === types_1.UploadEventType.COMPLETE; var contentType = e.xhr.getResponseHeader('Content-Type') || ''; var response = config.responseType === types_1.ResponseType.JSON || contentType.indexOf('application/json') > -1 ? JSON.parse(e.xhr.responseText) : e.xhr.responseText; var headers = e.xhr.getAllResponseHeaders(); var headerArr = headers.split('\n'); var headerMap_1 = {}; headerArr.forEach(function (e) { var _a = e.split(':'), key = _a[0], value = _a[1]; if (key && value) { headerMap_1[key.trim()] = value.trim(); } }); var httpResponse = new http_response_1.HttpResponseImpl(e.type === types_1.UploadEventType.COMPLETE, e.xhr.status, headerMap_1, isComplete ? response : null, isComplete ? null : response); var ret = config.reduce(httpResponse, _this.state); _this.notifyResponse(config, e.type === types_1.UploadEventType.COMPLETE ? key + "-ok" : key + "-ng", httpResponse, ret, isComplete ? subjectsOK : subjectsNG); } else { var httpResponse = new http_response_1.HttpUploadProgressImpl(e.event, e.xhr); _this.notifyUploading(config, key + "-uploading", httpResponse, subjectsUploading); } }, function (error) { return console.error(error); }); }; HttpHandler.prototype.notifyUploading = function (config, key, progress, subjects) { var _this = this; subjects.forEach(function (subject) { return subject.next({ data: progress, state: _this.state }); }); this.subject && this.subject.notify({ type: key, payload: progress }); }; HttpHandler.prototype.notifyResponse = function (config, key, httpResponse, results, subjects) { var _this = this; subjects.forEach(function (subject) { return subject.next({ data: results, state: _this.state }); }); this.subject && this.subject.notify({ type: key, payload: results }); }; HttpHandler.prototype.handleResponse = function (config, key, succeededHandler, errorHandler) { return __awaiter(this, void 0, void 0, function () { var res, u, resp, ret, err_1, resp, e, e_1; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 4, , 11]); return [4 /*yield*/, (function () { switch (config.method) { case types_1.HttpMethod.GET: return _this.get(config, key); case types_1.HttpMethod.POST: return _this.post(config, key); case types_1.HttpMethod.PUT: return _this.put(config, key); case types_1.HttpMethod.PATCH: return _this.patch(config, key); case types_1.HttpMethod.DELETE: return _this.delete(config, key); default: return _this.get(config, key); } })()]; case 1: res = _a.sent(); if (!res.ok) { throw res; } // For IE|Edge if (!res.url) { u = 'ur' + 'l'; try { res[u] = config.url; } catch (e) { } } resp = this.getResponse(config, key, config.responseType, res); if (!(resp && resp.then)) return [3 /*break*/, 3]; return [4 /*yield*/, resp]; case 2: ret = _a.sent(); succeededHandler(res, ret); _a.label = 3; case 3: return [3 /*break*/, 11]; case 4: err_1 = _a.sent(); if (!(err_1 && typeof err_1.json === 'function')) return [3 /*break*/, 9]; resp = this.getResponse(config, key, this.getResponseTypeFromHeader(err_1), err_1); if (!(resp && resp.then)) return [3 /*break*/, 8]; _a.label = 5; case 5: _a.trys.push([5, 7, , 8]); return [4 /*yield*/, resp]; case 6: e = _a.sent(); errorHandler(err_1, e); return [3 /*break*/, 8]; case 7: e_1 = _a.sent(); errorHandler(err_1, e_1); return [3 /*break*/, 8]; case 8: return [3 /*break*/, 10]; case 9: errorHandler(err_1, err_1); _a.label = 10; case 10: return [3 /*break*/, 11]; case 11: return [2 /*return*/]; } }); }); }; HttpHandler.prototype.processHeaders = function (res) { var headers = {}; res.headers.forEach(function (v, k) { return (headers[k] = v); }); return headers; }; HttpHandler.prototype.getFetcher = function () { return fetch; }; /** * Send GET request. * @data url Target url. * @data data GET parameters. * @returns Promise that return response. */ HttpHandler.prototype.get = function (_a, key) { var url = _a.url, _b = _a.headers, headers = _b === void 0 ? {} : _b, _c = _a.data, data = _c === void 0 ? null : _c, mode = _a.mode; return this.getFetcher()(data ? "" + url + qs_1.qs(data) : url, { method: 'GET', headers: headers, mode: mode || 'same-origin', }); }; /** * Send POST request. * @data url Target url. * @data data POST body. * @returns Promise that return response. */ HttpHandler.prototype.post = function (_a, key) { var url = _a.url, _b = _a.headers, headers = _b === void 0 ? {} : _b, _c = _a.data, data = _c === void 0 ? {} : _c, _d = _a.json, json = _d === void 0 ? true : _d, _e = _a.form, form = _e === void 0 ? false : _e, mode = _a.mode; return this.getFetcher()(url, { headers: headers, method: 'POST', mode: mode || 'same-origin', body: json ? JSON.stringify(data) : form ? qs_1.qs(data) : data, }); }; /** * Send PUT request. * @data url Target url. * @data data PUT body. * @returns Promise that return response. */ HttpHandler.prototype.put = function (_a, key) { var url = _a.url, _b = _a.headers, headers = _b === void 0 ? {} : _b, _c = _a.data, data = _c === void 0 ? {} : _c, _d = _a.json, json = _d === void 0 ? true : _d, _e = _a.form, form = _e === void 0 ? false : _e, mode = _a.mode; return this.getFetcher()(url, { headers: headers, method: 'PUT', mode: mode || 'same-origin', body: json ? JSON.stringify(data) : form ? qs_1.qs(data) : data, }); }; /** * Send PATCH request. * @data url Target url. * @data data PUT body. * @returns Promise that return response. */ HttpHandler.prototype.patch = function (_a, key) { var url = _a.url, _b = _a.headers, headers = _b === void 0 ? {} : _b, _c = _a.data, data = _c === void 0 ? {} : _c, _d = _a.json, json = _d === void 0 ? true : _d, _e = _a.form, form = _e === void 0 ? false : _e, mode = _a.mode; return this.getFetcher()(url, { headers: headers, method: 'PATCH', mode: mode || 'same-origin', body: json ? JSON.stringify(data) : form ? qs_1.qs(data) : data, }); }; /** * Send DELETE request. * @data url Target url. * @data data PUT body. * @returns Promise that return response. */ HttpHandler.prototype.delete = function (_a, key) { var url = _a.url, _b = _a.headers, headers = _b === void 0 ? {} : _b, _c = _a.data, data = _c === void 0 ? {} : _c, _d = _a.json, json = _d === void 0 ? true : _d, _e = _a.form, form = _e === void 0 ? false : _e, mode = _a.mode; var req = { headers: headers, method: 'DELETE', mode: mode || 'same-origin', }; if (core_1.isDefined(data)) { req.body = json ? JSON.stringify(data) : form ? qs_1.qs(data) : data; } return this.getFetcher()(url, req); }; HttpHandler.prototype.upload = function (_a, key) { var method = _a.method, url = _a.url, _b = _a.headers, headers = _b === void 0 ? {} : _b, _c = _a.data, data = _c === void 0 ? {} : _c, mode = _a.mode; var xhr = new XMLHttpRequest(); var subject = new rxjs_1.Subject(); var events = {}; var addEvent = function (xhr, type, fn, dispose) { if (dispose === void 0) { dispose = false; } events[type] = function (e) { if (dispose) { for (var key_1 in events) { xhr.removeEventListener(key_1, events[key_1]); } } fn(e); }; xhr.addEventListener(type, events[type], false); }; if (xhr.upload) { addEvent(xhr.upload, 'progress', function (e) { return subject.next({ type: types_1.UploadEventType.PROGRESS, event: e, xhr: xhr }); }); } addEvent(xhr, 'error', function (e) { return subject.next({ type: types_1.UploadEventType.ERROR, event: e, xhr: xhr }); }, true); addEvent(xhr, 'abort', function (e) { return subject.next({ type: types_1.UploadEventType.ABORT, event: e, xhr: xhr }); }, true); addEvent(xhr, 'load', function (e) { if (!xhr.upload) { subject.next({ type: types_1.UploadEventType.PROGRESS, event: { total: 1, loaded: 1 }, xhr: xhr, }); } subject.next({ type: types_1.UploadEventType.COMPLETE, event: e, xhr: xhr }); }, true); xhr.open(types_1.HttpMethod[method], url, true); for (var key_2 in headers) { xhr.setRequestHeader(key_2, headers[key_2]); } xhr.send(data); return Promise.resolve(subject); }; /** * Get proper response from fetch response body. * @param responseType The type of response. ex. ARRAY_BUFFER, BLOB, etc... * @param res Http response. * @returns */ HttpHandler.prototype.getResponse = function (config, key, responseType, res) { switch (responseType) { case types_1.ResponseType.ARRAY_BUFFER: return res.arrayBuffer(); case types_1.ResponseType.BLOB: return res.blob(); case types_1.ResponseType.FORM_DATA: return res.formData(); case types_1.ResponseType.JSON: return res.json(); case types_1.ResponseType.TEXT: return res.text(); case types_1.ResponseType.STREAM: return Promise.resolve(res.body); default: return res.text(); } }; HttpHandler.prototype.getResponseTypeFromHeader = function (res) { var mime = res.headers.get('content-type'); if (!mime || mime.indexOf('text/plain') > -1) { return types_1.ResponseType.TEXT; } if (mime.indexOf('text/json') > -1 || mime.indexOf('application/json') > -1) { return types_1.ResponseType.JSON; } if (/^(?:image|audio|video|(?:application\/zip)|(?:application\/octet-stream))/.test(mime)) { return types_1.ResponseType.BLOB; } return types_1.ResponseType.TEXT; }; HttpHandler.displayName = 'HttpHandler'; HttpHandler._maxHistoryLength = 10; return HttpHandler; }(core_1.StateHandler)); exports.HttpHandler = HttpHandler;