// ==UserScript==
// @name HttpRequest Library
// @namespace hoehleg.userscripts.private
// @version 0.5
// @description HttpRequest for any type of request and HttpRequestHTML to request webpage. Supports caching of responses for a given period and paging.
// @author Gerrit Höhle
//
// @grant GM_xmlhttpRequest
//
// ==/UserScript==
/* jslint esversion: 9 */
const HttpRequest = (() => {
const urlWithParams = (url, paramsObject) => {
const params = Object.entries(paramsObject).map(([key, value]) => key + '=' + value).join('&');
return params.length ? url + '?' + params : url;
};
const responsesCache = new Map();
const requestKey = ({ method, url, params, data }) => `${method}:${urlWithParams(url, params)}:DATA:${data}`;
return class HttpRequest {
constructor({
method,
url,
headers = {},
data = '',
params = {},
responseType = null,
keepInCacheTimoutMs = 0,
} = {}) {
Object.assign(this, { method, url, headers, data, params, responseType, keepInCacheTimoutMs });
}
async send() {
if (!this.method || !this.url) {
return await Promise.reject("invalid request");
}
return await new Promise((resolve, reject) => {
const method = this.method.toUpperCase();
const responseType = this.responseType;
let { url, headers, data } = this;
const onload = (response) => {
switch (response.status) {
case 200:
if (this.keepInCacheTimoutMs) {
const key = requestKey(this);
responsesCache.set(key, response);
if (this.keepInCacheTimoutMs > 0) {
setTimeout(() => responsesCache.delete(key), this.keepInCacheTimoutMs);
}
}
break;
case 304:
if (this.isCached()) {
response = this.readFromCache();
response.status = 304;
}
break;
default:
reject(`Status: ${response.status}, Error: ${response.statusText}`);
return;
}
resolve(response);
};
const onerror = (errorEvent) => {
console.log(errorEvent);
reject("network error");
};
switch (method) {
case 'GET':
if (this.params) {
url = urlWithParams(url, this.params);
}
break;
case 'POST':
case 'PUT':
headers = Object.assign({ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, headers || {});
if (this.params) {
data = JSON.stringify({ ...data, ...this.params });
}
break;
}
url = encodeURI(url);
GM_xmlhttpRequest({ method, url, onload, onerror, headers, data, responseType });
});
}
isCached() {
return responsesCache.has(requestKey(this));
}
readFromCache() {
return responsesCache.get(requestKey(this));
}
static async send(...args) {
return await new HttpRequest(...args).send();
}
};
})();
class HttpRequestHtml extends HttpRequest {
/**
* @param {HttpRequestHtmlParams} param0
*/
constructor({
url,
params = {},
keepInCacheTimoutMs = 0,
pageNr = 0,
pagesMaxCount = 1,
resultTransformer = (resp, _httpRequestHtml) => resp,
hasNextPage = (_resp, _httpRequestHtml, _lastResult) => false,
urlConfiguratorForPageNr = (url, _pageNr) => url,
paramsConfiguratorForPageNr = (params, _pageNr) => params,
} = {}) {
super({ method: 'GET', url, params, keepInCacheTimoutMs });
Object.assign(this, {
pageNr,
pagesMaxCount: Math.max(0, pagesMaxCount),
resultTransformer,
hasNextPage,
urlConfiguratorForPageNr,
paramsConfiguratorForPageNr,
});
}
clone() {
return new HttpRequestHtml({ ...this });
}
/**
* @returns {Promise<HttpRequestHtmlResponse|object|Array<object>}
*/
async send() {
const results = [];
let response = null, requestForPage = null;
for (let pageNr = this.pageNr; pageNr < this.pageNr + this.pagesMaxCount; pageNr++) {
requestForPage = Object.assign(this.clone(), {
url: this.urlConfiguratorForPageNr(this.url, pageNr),
params: this.paramsConfiguratorForPageNr({ ...this.params }, pageNr)
});
response = await HttpRequest.prototype.send.call(requestForPage);
if (response.status == 200 || response.status == 304) {
response.html = new DOMParser().parseFromString(response.responseText, 'text/html');
}
const resultForPage = this.resultTransformer(response, requestForPage);
results.push(resultForPage);
if (!this.hasNextPage(response, requestForPage, resultForPage)) {
break;
}
}
return this.pagesMaxCount > 1 ? results : results[0];
}
/**
* @param {HttpRequestHtmlParams} param0
* @returns {Promise<HttpRequestHtmlResponse|object|Array<object>}
*/
static async send(...args) {
return await new HttpRequestHtml(...args).send();
}
}
class HttpRequestJSON extends HttpRequest {
/** @param {HttpRequestJSONParams} param0 */
constructor({
method = 'GET',
protocol = 'http:',
hostname = 'localhost',
port = null,
pathname = '',
params = {},
fallbackResult = null
}) {
const url = Object.assign(new URL("http://example.com"), { protocol, hostname, port, pathname });
super({ method, url: url.href, params });
this.fallbackResult = fallbackResult;
}
/** @returns {Promise<any>} */
async send() {
const response = await super.send();
if (!response || !response.responseText) {
return this.fallbackResult;
}
try {
return JSON.parse(response.responseText);
} catch (error) {
console.log(error);
return this.fallbackResult;
}
}
/**
*
* @param {HttpRequestJSONParams} param0
* @returns {Promise<any>}
*/
static async send(param0) {
return await new HttpRequestJSON(param0).send();
}
}