/*! Posterior - v0.23.0 - 2019-12-04 * http://esha.github.io/posterior/ * Copyright (c) 2019 ESHA Research; Licensed MIT */ (function(W) { "use strict"; var store = W.store || function(){}, D = W.document, Posterior = function(config, name) { if (typeof config === "string") { config = { url: config }; } if (typeof name === "function") { config.then = name; } return Posterior.api(config, name); }; var XHR = Posterior.xhr = function(cfg) { return XHR.main(cfg); }; XHR.ctor = XMLHttpRequest; XHR.main = function(cfg) { var xhr, promise; if (cfg.cache) { xhr = store(XHR.key(cfg)); if (xhr) { promise = Promise.resolve(xhr); } } if (!promise) { xhr = new XHR.ctor(); XHR.config(xhr, cfg); promise = XHR.promise(xhr, cfg); if (cfg.cache) { promise.then(function() { XHR.cache(xhr); }); } } if (cfg.then) { promise = promise.then(cfg.then); } if (cfg.catch) { // call catch with cfg as context and xhr as second arg promise = promise.catch(function caught(error) { return cfg.catch.call(cfg, error, xhr); }); } promise.xhr = xhr; return promise; }; XHR.config = function(xhr, cfg) { xhr.open(XHR.method(cfg), XHR.url(cfg), 'async' in cfg ? cfg.async : true, cfg.username, cfg.password); xhr.cfg = cfg; cfg.xhr = xhr; for (var prop in cfg) { var value = cfg[prop]; if (typeof value === "function") { if (prop === "then" || prop === "catch") { cfg[prop] = value.bind(cfg); } xhr.addEventListener(prop, value.bind(xhr)); } else if (prop in xhr) { xhr[prop] = value; } } if (cfg.mimeType) { xhr.overrideMimeType(cfg.mimeType); } // make sure headers we set are also exposed in the config var headers = cfg.headers || (cfg.headers = {}); if (cfg.requestedWith !== false) { headers['X-Requested-With'] = cfg.requestedWith || 'XMLHttpRequest'; } if (cfg.json !== false) { try { xhr.responseType = 'json';// unsupported by phantomjs (webkit) } catch (e) {} // don't squash existing headers if (!headers.Accept) { headers.Accept = 'application/json'; } if (!headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } } for (var header in headers) { xhr.setRequestHeader(header, headers[header]); } }; XHR.method = function(cfg) { return XHR.methodMap[cfg.method] || cfg.method || 'GET'; }; XHR.methodMap = { PATCH: 'POST' }; XHR.url = function(cfg) { var url = cfg.url, was; while (was !== url) { was = url; url = url.replace(/([\/\?\&]|^)[^\/\.\?\&]+[\/\?\&]?\.\.([\/\?\&]?)/, '$1'); } return url; }; XHR.promise = function(xhr, cfg) { return new Promise(function(resolve, reject) { var fail = function(e) { if (cfg.retry) { XHR.retry(cfg, cfg.retry, events, fail); } else if (cfg.debug === 'capture') { XHR.capture('error', xhr, cfg, e); reject(e); } else { reject(xhr.error = e); } }, succeed = cfg.debug === 'capture' ? function(result) { XHR.capture('success', xhr, cfg, result); resolve(result); } : resolve, events = { error: fail, timeout: fail, loadstart: XHR.start, loadend: XHR.end, load: XHR.load(cfg, succeed, fail) }; XHR[cfg.throttle ? 'throttle' : 'run'](xhr, cfg, events, fail); }); }; XHR.retry = function(cfg, retry, events, fail) { if (typeof retry !== 'object') { retry = cfg.retry = {}; } retry.wait = 'wait' in retry ? retry.wait : 1000; retry.limit = 'limit' in retry ? retry.limit : 3; retry.count = 'count' in retry ? retry.count : 0; if (retry.limit > retry.count++) { setTimeout(function retryAfterWait() { var xhr = new XHR.ctor(); XHR.config(xhr, cfg); XHR.run(xhr, cfg, events, fail); }, retry.wait *= 2); } }; XHR.throttle = function(xhr, cfg, events, fail) { var all = XHR.throttles || (XHR.throttles = {}), throttle = cfg.throttle, record = all[throttle.key]; if (!record) { record = all[throttle.key] = { queue: 0, lastRun: Date.now() - throttle.ms }; } record.queue++; var next = (record.lastRun + (record.queue * throttle.ms)) - Date.now(), runFn = function throttled() { XHR.run(xhr, cfg, events, fail); record.queue--; record.lastRun = Date.now(); }; if (next > 10) { setTimeout(runFn, next); } else { runFn(); } }; XHR.run = function(xhr, cfg, events, fail) { for (var event in events) { xhr.addEventListener(event, events[event]); } try { xhr.send(XHR.data(cfg)); } catch (e) { fail(e); } }; XHR.load = function(cfg, resolve, reject) { return function() { try { // cfg status code mapping (e.g. {0: 200} for file://) var xhr = cfg.xhr, status = cfg[xhr.status] || xhr.status, json = cfg.json !== false; if (typeof status === "function") { // support status code specific hook functions status = status(xhr) || xhr.status; } if (status >= 200 && status < 300) { if (json && typeof xhr.response !== "object") { XHR.forceJSONResponse(xhr); } var data = xhr.responseType ? xhr.response : json ? xhr.responseObject : xhr.responseText; if (json && data === null && !cfg.allowEmptyResponses) { reject('Presumed syntax error in JSON response, suppressed by your browser.'); } else { if (cfg.responseData && XHR.isData(data)) { var ret = xhr.responseData = cfg.responseData(data, xhr); data = ret === undefined ? data : ret; } resolve(XHR.isData(data) ? data : xhr); } } else { var error; if (cfg.failure) { // allow failure listener to set result error = cfg.failure(status, xhr); } reject(error !== undefined ? error : status); } } catch (e) { reject(e); } }; }; XHR.forceJSONResponse = function(xhr) { try { delete xhr.response; Object.defineProperty(xhr, 'response', { value: xhr.responseObject, enumerable: true, configurable: true }); } catch (e) {} }; XHR.isData = function(data) { // reject as impossible output by JSON.stringify, invalid input to JSON.parse return !(data === undefined || data === ''); }; XHR.data = function(cfg) { var data = cfg.data; if (cfg.requestData) { var ret = cfg.requestData(data); data = ret === undefined ? data : ret;// return new object or keep old } if (data instanceof Object && data.hasOwnProperty('toString')) { data = data.toString(); } else if (data !== undefined && typeof data !== 'string') { data = JSON.stringify(data); } return cfg.requestBody = data || ''; }; XHR.properties = { responseObject: { get: function() { var response = null; try { if (this.responseText) { response = JSON.parse(this.responseText); } } catch (e) {} if (response === null && typeof this.response === 'object') { response = this.response; } Object.defineProperty(this, 'responseObject', {value:response}); return response; }, enumerable: true, configurable: true }, responseHeaders: { get: function() { var headers = {}, all = this.getAllResponseHeaders().trim().split('\n'); for (var i=0,m=all.length, header; i= 0) { API.meta[prop.charAt(0)](meta, metaCfg); prop = prop.substring(1); } if (typeof meta.value === "function" && API.get(metaCfg, "debug")) { // add logging meta.value = API.debug(meta.name, meta.value); } // let config props be accessed from the api function if (!(prop in api)) { API.getter(api, prop); } metaCfg[prop] = meta; }; function isCaps(s) { return s.charAt(0) !== s.charAt(0).toLowerCase(); } API.meta = { chars: "!@_".split(""), _: function(meta) { meta.private = true; }, "!": function(meta) { meta.root = true; }, "@": function(meta, metaCfg) { meta.root = true; // extensions are self-combining, so act as roots metaCfg._fn[meta.name] = meta.value = API.build(meta.value, metaCfg, meta.name); } }; API.getter = function(fn, name) { try { Object.defineProperty(fn, name, { get: function() { return API.get(fn.metaCfg, name); }, configurable: true }); } catch (e) {} // ignore failures }; API.log = function(args, level) { var console = W.console, log = console && console[level || "log"]; if (log) { log.apply(console, args); } }; API.debug = function(name, fn) { return function debug(arg) { var args; try { var ret = fn.apply(this, arguments); if (ret !== undefined && ret !== arg) { args = [name + "("]; args.push.apply(args, arguments); args.push(") resolved to ", ret); API.log(args, "debug"); } return ret; } catch (e) { args = Array.prototype.concat.apply([name, e], arguments); API.log(args, "error"); throw e; } }; }; // end build-time functions // start call-time functions API.main = function(fn, args) { if (fn.metaCfg._singletonResult) { return Promise.resolve(fn.metaCfg._singletonResult); } var cfg = API.getAll(fn.metaCfg); cfg._fn = fn; // data must be an object or array cfg._args = args; cfg.data = (args.length > 1 || typeof args[0] !== "object") ? Array.prototype.slice.call(args) : args[0]; API.process(cfg); var promise = API.promise(cfg, fn); if (cfg.singleton) { promise.then(function(result) { fn.metaCfg._singletonResult = result; }); } return promise; }; API.getAll = function(metaCfg, inheriting) { var cfg = metaCfg._parent ? API.getAll(metaCfg._parent, true) : {}; for (var prop in metaCfg) { // don't copy _fn, or _parent if (prop.charAt(0) !== "_") { var meta = metaCfg[prop]; if (meta.root || prop === "name") { // name's are pre-combined cfg[prop] = meta.value !== undefined ? meta.value : meta; } else if (!inheriting || !meta.private) { cfg[prop] = API.combine(cfg[prop], meta.value, cfg); } } } return cfg; }; API.process = function(cfg) { if (cfg.configure) { // use meta config as context, pass in run cfg cfg.configure.call(cfg._fn.metaCfg, cfg); } for (var name in cfg) { var value = cfg[name]; if (typeof value === "string") { value = cfg[name] = API.resolve(value, cfg.data, cfg, cfg.consumeData); } } }; API.promise = function(cfg, fn) { var deps = cfg.requires; return deps ? Promise.all(deps.map(API.require.bind(cfg))).then(function() { return API.follow(cfg, fn); }) : API.follow(cfg, fn); }; API.follow = function(cfg, fn) { if (cfg.follows) { var follows = cfg.follows, leader; if (typeof follows === "object") { leader = follows.source; follows = follows.path; } if (!leader) { leader = fn.metaCfg._parent._fn; } if (leader) { var lead = leader.apply(null, cfg._args || []); return lead.then(function follow(resource) { cfg.url = follows && eval('resource.'+follows) || resource; return XHR(cfg); }); } else { return Promise.reject("Cannot follow link relation without a source function."); } } else { return XHR(cfg); } }; API.require = function(req) { try { if (typeof req === 'string') { req = eval('window.'+req); } if (typeof req === 'function') { req = req() || req; } return req ? Promise.resolve(req) : Promise.reject(req); } catch (e) { return Promise.reject(e); } }; API.resolve = function(string, data, cfg, consume) { var key, str = string, re = /\$?\{([^}]+)\}/g, used = []; while ((key = re.exec(string))) { key = key[1]; var val = null; if (key in data) { val = data[key]; if (consume !== false && used.indexOf(key) < 0) { used.push(key); } } else { try { val = eval('data.'+key); // escape key for replace call at end key = key.replace(/(\[|\.)/g, '\\$1'); } catch (e) {} if (val === undefined && cfg && key in cfg) { val = cfg[key]; } } if (val === null || val === undefined) { val = ''; } str = str.replace(new RegExp('\\$?\\{'+key+'}'), val); } if (consume !== false && used.length > 0) { if (Array.isArray(data)) { used.sort(); for (var i=used.length-1; i>=0; i--) { data.splice(used[i], 1); } } else { for (var j=0; j