/* global IITC -- eslint */
/**
* Namespace providing portal detail retrieval with caching and request de-duplication.
*
* @memberof IITC.portal
* @namespace details
*/
let cache;
const requestQueue = {};
/**
* Sets up the portal detail handler, initializing the cache.
*
* @memberof IITC.portal.details
*/
const setup = function () {
cache = new window.DataCache();
cache.startExpireInterval(20);
};
/**
* Retrieves portal details from cache by GUID.
*
* @memberof IITC.portal.details
* @param {string} guid - The Global Unique Identifier of the portal.
* @returns Cached portal details if available.
*/
const get = function (guid) {
return cache.get(guid);
};
/**
* Stores portal details in the cache.
*
* @memberof IITC.portal.details
* @param {string} guid - The Global Unique Identifier of the portal.
* @param {object} dict - The portal detail data.
* @returns Result of cache storage operation.
*/
const store = function (guid, dict) {
return cache.store(guid, dict);
};
/**
* Checks if portal details are fresh in the cache.
*
* @memberof IITC.portal.details
* @param {string} guid - The Global Unique Identifier of the portal.
* @returns {boolean} True if details are fresh, false otherwise.
*/
const isFresh = function (guid) {
return cache.isFresh(guid);
};
const remove = function (guid) {
return cache.remove(guid);
};
const handleResponseSuccess = function (deferred, guid, data, prefetch) {
if (!data || data.error || !data.result) {
handleResponseFailure(deferred, guid, data);
return;
}
// Parse portal details
const dict = window.decodeArray.portal(data.result, 'detailed');
cache.store(guid, dict);
// entity format, as used in map data
const ent = [guid, data.result[13], data.result];
const portal = window.mapDataRequest.render.createPortalEntity(ent, 'detailed');
deferred.resolve(portal.options.data);
window.runHooks('portalDetailLoaded', { guid: guid, success: true, details: portal.options.data, ent: ent, portal: portal });
// prefetch portal image
if (prefetch && portal.options.data.image) {
new Image().src = portal.options.data.image;
}
};
const handleResponseFailure = function (deferred, guid, data) {
if (data && data.error === 'RETRY') {
// server asked us to try again
doRequest(deferred, guid);
} else {
deferred.reject();
window.runHooks('portalDetailLoaded', { guid: guid, success: false });
}
};
const doRequest = function (deferred, guid, prefetch) {
window.postAjax(
'getPortalDetails',
{ guid: guid },
function (data) {
handleResponseSuccess(deferred, guid, data, prefetch);
},
function () {
handleResponseFailure(deferred, guid);
}
);
};
/**
* Decorates a native promise with jQuery-Deferred-style `.done`/`.fail`/`.always` methods,
* so plugins can keep chaining `request(guid).done(...)` while core code uses `await`/`.then`
*
* Only these three consumption methods are provided (not `.state`/`.progress`/`.pipe`/`.promise`);
* each forwards the resolved value or rejection reason to its callbacks and returns the same promise,
* so calls can be chained as in jQuery
*
* @param {Promise} promise - Native promise to decorate
* @returns {Promise} The same promise instance, with `.done`, `.fail` and `.always` attached
*/
const withJQueryCallbacks = function (promise) {
promise.done = function (...callbacks) {
// keep the empty rejection handler: .done reacts only to fulfilment (as in jQuery)
// and must not turn a rejected request into an unhandled rejection
callbacks.forEach((cb) => promise.then(cb, () => {}));
return promise;
};
promise.fail = function (...callbacks) {
callbacks.forEach((cb) => promise.then(undefined, cb));
return promise;
};
promise.always = function (...callbacks) {
callbacks.forEach((cb) => promise.then(cb, cb));
return promise;
};
return promise;
};
/**
* Requests detailed information for a specific portal. If the information is not already being requested,
* it initiates a new request. Returns a promise that resolves with the portal details.
*
* The returned promise is native, additionally exposing jQuery-Deferred-style `.done`/`.fail`/`.always`
* methods for backward compatibility
*
* @memberof IITC.portal.details
* @param {string} guid - The Global Unique Identifier of the portal.
* @returns {Promise} A promise that resolves with the portal details upon successful retrieval or rejection on failure.
*/
const request = function (guid, prefetch = false) {
if (!requestQueue[guid]) {
let resolve;
let reject;
const promise = withJQueryCallbacks(
new Promise((res, rej) => {
resolve = res;
reject = rej;
})
);
// drop from the queue once settled so later calls trigger a fresh request
promise.always(function () {
delete requestQueue[guid];
});
requestQueue[guid] = promise;
doRequest({ resolve, reject }, guid, prefetch);
}
return requestQueue[guid];
};
IITC.portal.details = {
setup,
get,
store,
isFresh,
remove,
request,
};
IITC.registerLegacyAliases(IITC.portal, { portalDetail: 'details' });