bbop-rest-manager
Version:
This is the 'new' version of the manager event model for BBOP systems.
246 lines (245 loc) • 9.32 kB
JavaScript
import bbop from "bbop-core";
import us from "underscore";
import registry from "bbop-registry";
//#region src/manager.js
/**
* Generic BBOP manager for dealing with basic generic REST calls.
* This specific one is designed to be overridden by its subclasses.
* This one pretty much just uses its incoming resource string as the data.
* Mostly for testing purposes.
*
* Both a <bbop-rest-response> (or clean error data) and the manager
* itself (this as anchor) should be passed to the callbacks.
*
* @module bbop-rest-manager
*/
function warnDeprecated(message) {
console.warn(message);
}
function normalizeHeaders(headerPairs) {
return Object.fromEntries(headerPairs || []);
}
function methodWithDefault(method) {
if (!method) return "GET";
return method.toUpperCase();
}
function buildUrl(resource, payload, method) {
if (method !== "GET" || us.isEmpty(payload)) return resource;
var url = new URL(resource);
var params = new URLSearchParams(url.search);
Object.entries(payload).forEach(function([key, value]) {
if (Array.isArray(value)) value.forEach(function(item) {
params.append(key, item);
});
else if (typeof value !== "undefined" && value !== null) params.append(key, value);
});
url.search = params.toString();
return url.toString();
}
function buildRequestInit(payload, method, headers) {
var requestHeaders = normalizeHeaders(headers);
var init = {
method,
headers: requestHeaders
};
if (method === "GET") return init;
if (!us.isEmpty(payload)) {
var body = new URLSearchParams();
Object.entries(payload).forEach(function([key, value]) {
if (Array.isArray(value)) value.forEach(function(item) {
body.append(key, item);
});
else if (typeof value !== "undefined" && value !== null) body.append(key, value);
});
if (!Object.keys(requestHeaders).some(function(header) {
return header.toLowerCase() === "content-type";
})) requestHeaders["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8";
init.body = body;
}
return init;
}
function normalizeErrorMessage(error) {
if (!error) return "unknown fetch error";
if (error instanceof Error) return error.message;
return String(error);
}
/**
* Constructor for the REST manager.
*
* See also: module:bbop-registry
*
* @constructor
* @param {Object} response_handler - the response handler class to use for each call
* @returns {Object} rest manager object
*/
function manager_base(response_handler) {
registry.call(this, ["success", "error"]);
this._is_a = "bbop-rest-manager.base";
var anchor = this;
this._logger = new bbop.logger(this._is_a);
this._logger.DEBUG = false;
function ll(str) {
anchor._logger.kvetch(str);
}
this._response_handler = response_handler;
this._qurl = null;
this._qpayload = {};
this._qmethod = "GET";
this._headers = [];
this._safety = false;
this.debug = function(p) {
if (p === true || p === false) this._logger.DEBUG = p;
return this._logger.DEBUG;
};
this._ensure_arguments = function(url, payload, method, headers) {
ll("ensure arguments...");
if (typeof url !== "undefined") this.resource(url);
if (typeof payload !== "undefined") this.payload(payload);
if (typeof method !== "undefined") this.method(method);
if (typeof headers !== "undefined") this.headers(headers);
if (!this.resource()) throw new Error("must have resource defined");
};
this._apply_callbacks_by_response = function(response) {
ll("apply callbacks by response...");
if (response && response.okay()) anchor.apply_callbacks("success", [response, anchor]);
else anchor.apply_callbacks("error", [response, anchor]);
};
this.resource = function(in_url) {
ll("resource called with: " + in_url);
if (typeof in_url !== "undefined" && bbop.what_is(in_url) === "string") anchor._qurl = in_url;
return anchor._qurl;
};
this.payload = function(payload) {
ll("payload called with: " + payload);
if (bbop.is_defined(payload) && bbop.what_is(payload) === "object") anchor._qpayload = payload;
return bbop.clone(anchor._qpayload);
};
this.method = function(method) {
ll("method called with: " + method);
if (bbop.is_defined(method) && bbop.what_is(method) === "string") anchor._qmethod = methodWithDefault(method);
return anchor._qmethod;
};
this.headers = function(in_headers) {
ll("headers called with: " + in_headers);
if (in_headers && us.isArray(in_headers)) anchor._headers = in_headers;
return anchor._headers;
};
this.run_promise_functions = async function(promise_function_stack, accumulator_function, final_function, error_function) {
var initialCount = promise_function_stack.length || 0;
while (!us.isEmpty(promise_function_stack)) {
var promise_runner = promise_function_stack.shift();
try {
accumulator_function(await promise_runner(), anchor);
} catch (err) {
if (err) error_function(err, anchor);
return initialCount;
}
}
final_function(anchor);
return initialCount;
};
}
bbop.extend(manager_base, registry);
manager_base.prototype.fetch = function(url, payload, method, headers) {
this._logger.kvetch("called fetch");
this._ensure_arguments(url, payload, method, headers);
var response = new this._response_handler(this.payload());
response.okay(true);
response.message("empty");
response.message_type("success");
this._apply_callbacks_by_response(response);
return response;
};
manager_base.prototype.start = function(url, payload, method, headers) {
this._ensure_arguments(url, payload, method, headers);
var response = new this._response_handler(this.payload());
response.okay(true);
response.message("empty");
response.message_type("success");
this._apply_callbacks_by_response(response);
return Promise.resolve(response);
};
var manager_fetch = function(response_handler) {
manager_base.call(this, response_handler);
this._is_a = "bbop-rest-manager.fetch";
};
bbop.extend(manager_fetch, manager_base);
manager_fetch.prototype.fetch = function(url, payload, method, headers) {
this._logger.kvetch("called fetch");
this.start(url, payload, method, headers);
return null;
};
manager_fetch.prototype.start = async function(url, payload, method, headers) {
var anchor = this;
this._ensure_arguments(url, payload, method, headers);
var requestMethod = methodWithDefault(anchor.method());
var requestUrl = buildUrl(anchor.resource(), anchor.payload(), requestMethod);
var requestInit = buildRequestInit(anchor.payload(), requestMethod, anchor.headers());
try {
var fetchResponse = await fetch(requestUrl, requestInit);
var rawText = await fetchResponse.text();
var response = new anchor._response_handler(rawText);
if (fetchResponse.ok && response && response.okay()) {
anchor.apply_callbacks("success", [response, anchor]);
return response;
}
if (!response) response = new anchor._response_handler(null);
response.okay(false);
response.message_type("error");
if (!rawText) response.message(`HTTP ${fetchResponse.status}`);
else if (fetchResponse.ok) response.message("bad response");
else response.message(`HTTP ${fetchResponse.status}`);
anchor.apply_callbacks("error", [response, anchor]);
return response;
} catch (error) {
var errorResponse = new anchor._response_handler(null);
errorResponse.okay(false);
errorResponse.message(normalizeErrorMessage(error));
errorResponse.message_type("error");
anchor.apply_callbacks("error", [errorResponse, anchor]);
return errorResponse;
}
};
var manager_node = function(response_handler) {
manager_fetch.call(this, response_handler);
this._is_a = "bbop-rest-manager.node";
};
bbop.extend(manager_node, manager_fetch);
var manager_sync_request = function(response_handler) {
manager_fetch.call(this, response_handler);
this._is_a = "bbop-rest-manager.sync_request";
};
bbop.extend(manager_sync_request, manager_fetch);
manager_sync_request.prototype.fetch = function(url, payload, method, headers) {
warnDeprecated("bbop-rest-manager.sync_request is deprecated; requests are now asynchronous and fetch() returns null. Use start() instead.");
this._logger.kvetch("called fetch");
manager_fetch.prototype.start.call(this, url, payload, method, headers);
return null;
};
manager_sync_request.prototype.start = function(url, payload, method, headers) {
warnDeprecated("bbop-rest-manager.sync_request is deprecated; requests are now asynchronous. Use start() as a promise-based API.");
return manager_fetch.prototype.start.call(this, url, payload, method, headers);
};
var manager_jquery = function(response_handler) {
manager_fetch.call(this, response_handler);
this._is_a = "bbop-rest-manager.jquery";
this._use_jsonp = false;
this._jsonp_callback = "json.wrf";
};
bbop.extend(manager_jquery, manager_fetch);
manager_jquery.prototype.use_jsonp = function(use_p) {
if (typeof use_p !== "undefined") warnDeprecated("bbop-rest-manager.jquery JSONP support has been removed; use_jsonp() is now a no-op.");
return this._use_jsonp;
};
manager_jquery.prototype.jsonp_callback = function(cstring) {
if (typeof cstring !== "undefined") warnDeprecated("bbop-rest-manager.jquery JSONP support has been removed; jsonp_callback() is now a no-op.");
return this._jsonp_callback;
};
var manager_default = {
base: manager_base,
node: manager_node,
sync_request: manager_sync_request,
jquery: manager_jquery
};
//#endregion
export { manager_default as default };