(function() {
/**
* @typedef {Object} Help4.widget.companionCore.SEN.CreatePubUrlParam
* @property {string} [serverBaseUrl] - base URL of the content server
* @property {string} [waId] - ID of workspace on the sever
* @property {string} [serverUrl] - URL to the content on the server
* @property {string} tag - to be used tag
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.MultifileRequest
* @property {string} url - URL on the server; relative to serverBaseUrl
* @property {string} [method = GET] - GET, POST, PUT, DELETE
* @property {string} [dataType] - e.g. text, xml, json, multipart
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.MultifileParams
* @property {string} serverBaseUrl - base URL of the content server
* @property {Help4.widget.companionCore.SEN.MultifileRequest[]} request - the request information
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.CatalogueResponse
* @property {*} [status] - response status on error
* @property {Help4.widget.help.catalogues.SEN.CatalogueProject[]} [response] - server response
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.ContextResponse
* @property {*} [status] - response status on error
* @property {Object[]} [response]
*/
/**
* @typedef {Document|Object} Help4.widget.companionCore.SEN.XmlResponse
* @property {*} [status] - response status on error
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.ProjectDataResponse
* @property {*} [status] - response status on error
* @property {Help4.widget.help.project.SEN.Project} [response]
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.TagResponse
* @property {*} [status] - response status on error
* @property {Object} [response]
* @property {Help4.widget.help.project.SEN.TagInfoList} response.resource
*/
/**
* @typedef {Help4.widget.companionCore.SEN.ContextResponse|Help4.widget.companionCore.SEN.XmlResponse|Help4.widget.companionCore.SEN.ProjectDataResponse|Help4.widget.companionCore.SEN.TagResponse|null} Help4.widget.companionCore.SEN.ProjectResponses
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.PubProjectResponse
* @property {Help4.widget.help.ContextTypes} contextType
* @property {Help4.widget.help.project.SEN.EntityTxt} entityTxt
* @property {Help4.widget.help.project.SEN.LessonJs} lessonJs
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.ProjectResponsePub
* @property {Help4.widget.companionCore.SEN.PubProjectResponse} context
* @property {Help4.widget.help.project.SEN.Project} project
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.ProjectResponseHead
* @property {Document} entityXml
* @property {Document} projectDpr
* @property {Help4.widget.help.project.SEN.Project} project
*/
/**
* @typedef {Object} Help4.widget.companionCore.SEN.ProjectResponse
* @property {?Help4.widget.companionCore.SEN.ProjectResponsePub} pub
* @property {?Help4.widget.companionCore.SEN.ProjectResponseHead} head
* @property {?Help4.widget.help.project.SEN.TagInfoList} tagInfo
*/
/**
* helper functionality for SEN backends
*/
Help4.widget.companionCore.SEN = class {
/**
* Header information for XHR to prohibit caching.
* @memberof Help4.widget.companionCore.SEN
*/
static NO_CACHE_HEADER = {
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
Pragma: 'no-cache'
}
/**
* extracts the server base url from a full server url
* @param {string} serverUrl
* @returns {string}
*/
static getServerBaseUrl(serverUrl) {
return serverUrl.replace(/\/(wa|pub)\/.*$/, '');
}
/**
* creates a public url from a non-public one
* @param {Help4.widget.companionCore.SEN.CreatePubUrlParam} params
* @returns {string}
*/
static createPubUrl({serverBaseUrl, waId, serverUrl, tag}) {
if (waId) {
return tag === 'published'
? `${serverBaseUrl}/pub/${waId}/`
: `${serverBaseUrl}/wa/${waId}/~tag/${tag}/`;
} else {
if (tag === 'published') { // XRAY-1486
// use wormhole
// - replace '/wa/<waid>'
// - with '/pub/<waid'
return serverUrl.replace(/\/wa\//, '/pub/');
} else { // XRAY-2483
// use long version
// - replace '/wa/<waid>'
// - with 'wa/<waid>/~tag/<tag>'
return serverUrl.replace(/(\/wa\/[^/]+)/, `$1/~tag/${tag}`);
}
}
}
/**
* @param {Help4.typedef.SystemConfiguration} config - the system configuration
* @param {Help4.widget.companionCore.SEN.MultifileParams} params - the request information
* @returns {Promise<*>}
*/
static doMultifileRequest({core: {multifile}}, {serverBaseUrl, request}) {
if (!multifile) return this.doMultifileWorkaround({serverBaseUrl, request});
return new Help4.Promise(resolve => {
/**
* @param {*} data
* @param {Help4.typedef.XmlHttpResponse} xhr
* @param {*} error
* @returns {Promise<void>}
*/
const afterRequest = async (data, xhr, error) => {
Help4.widget.companionCore.Core.broadcastXhrStatus(xhr);
if (error === 'error' && xhr && xhr.status !== 200) {
// multifile not supported here
data = await this.doMultifileWorkaround({serverBaseUrl, request});
}
resolve(data);
}
Help4.ajax.EnableNow({
headers: this.NO_CACHE_HEADER,
url: serverBaseUrl + '/multifile',
saml: true,
xcsrf: this.getXCSRF(serverBaseUrl),
method: 'POST',
dataType: 'multipart',
data: {request},
success: afterRequest,
error: afterRequest
});
});
}
/**
* @param {Help4.widget.companionCore.SEN.MultifileParams} params - the request information
* @returns {Promise<*>}
*/
static async doMultifileWorkaround({serverBaseUrl, request}) {
function* generateWorkaroundRequest() {
const {Core, SEN} = Help4.widget.companionCore;
for (const {url, method = 'GET', dataType} of request) {
const promise = new Help4.Promise(resolve => {
/**
* @param {*} data
* @param {Help4.typedef.XmlHttpResponse} xhr
*/
const afterRequest = (data, xhr) => {
Core.broadcastXhrStatus(xhr);
resolve(data);
};
Help4.ajax.Ajax({
headers: SEN.NO_CACHE_HEADER,
method,
url: serverBaseUrl + url,
saml: true,
dataType,
success: afterRequest,
error: afterRequest
});
});
yield {promise};
}
}
const {/** @type {Array<*>} */ array} = await Help4.awaitPromises(generateWorkaroundRequest);
return array;
}
/**
* @param {string} serverBaseUrl - the server base URL
* @returns {string}
*/
static getXCSRF(serverBaseUrl) {
const {Ajax} = Help4.ajax;
const url = serverBaseUrl + '/self';
let id = Ajax.getXCSRF(url);
if (id) return id;
id = Help4.createId();
Ajax.setXCSRF({id, url});
return id;
}
/**
* @param {string} url
* @returns {Promise<*>}
*/
static doGetRequest(url) {
return new Help4.Promise(resolve => {
/**
* @param {*} data
* @param {Help4.typedef.XmlHttpResponse} xhr
*/
const afterRequest = (data, xhr) => {
Help4.widget.companionCore.Core.broadcastXhrStatus(xhr);
resolve(data);
};
// XRAY-1490: do not use XCSRF ID here
Help4.ajax.Ajax({
url: url,
saml: true,
success: afterRequest,
error: afterRequest
});
});
}
/**
* @param {Help4.typedef.SystemConfiguration} config - the system configuration
* @param {boolean} [allowDuplicates = false] - whether to allow duplicate language codes
* @returns {string[]}
*/
static getLanguages({core: {language, defaultLanguage}}, allowDuplicates = false) {
const languagesList = [language.wpb].concat(defaultLanguage.wpb);
return allowDuplicates ? languagesList : Help4.uniqueArray(languagesList);
}
/**
* loads an SEN project
* @param {Help4.typedef.SystemConfiguration} config
* @param {string} serviceUrl
* @param {Help4.widget.help.catalogues.SEN.ProjectUrlConfig} request
* @returns {Promise<Help4.widget.companionCore.SEN.ProjectResponse|Help4.widget.companionCore.SEN.PubProjectResponse|null>}
*/
static async doProjectRequest(config, serviceUrl, {serverBaseUrl, pubUrl, headUrl = null}) {
/**
* @param {Help4.widget.companionCore.SEN.ContextResponse|null} serverResponse
* @returns {Help4.widget.companionCore.SEN.PubProjectResponse|null}
*/
const extractPubContextInfo = serverResponse => {
serverResponse = !serverResponse?.status && serverResponse?.response?.[0] || {};
/** @type {'HELP'|'TOUR'|null} */
let contextType = null;
/** @type {Help4.widget.help.project.SEN.EntityTxt|null} */
let entityTxt = null;
/** @type {Help4.widget.help.project.SEN.LessonJs|null} */
let lessonJs = null;
for (const [key, value] of Object.entries(serverResponse)) {
if (key.includes('entity.txt')) {
entityTxt = value;
} else if (key.includes('lesson.js')) {
lessonJs = value;
} else {
contextType = value;
}
}
return contextType && entityTxt && lessonJs
? {contextType, entityTxt, lessonJs}
: null;
}
if (!headUrl) {
// non-editor mode; download PUB only
const response = await this.doGetRequest(pubUrl);
/** @type {Help4.widget.companionCore.SEN.PubProjectResponse|null} */
return extractPubContextInfo(response);
} else {
// editor mode; download PUB and head
const len = serverBaseUrl.length;
serviceUrl = serviceUrl.substring(len);
pubUrl = pubUrl.substring(len);
headUrl = headUrl.substring(len);
const {playbackTag: tag} = config.core;
const pubUrl2 = this.createPubUrl({serverUrl: headUrl, tag}); // XRAY-3305
/** @type {Array<{url: string, dataType: ?string}>} */
const request = [
{url: pubUrl}, // "/pub/<waId>/.context?..."
{url: `${headUrl}/entity.xml`, dataType: 'xml'}, // "/wa/<waId>/project/<projectId>/entity.xml"
{url: `${headUrl}/project.dpr`, dataType: 'xml'}, // "/wa/<waId>/project/<projectId>/project.dpr"
{url: headUrl}, // "/wa/<waId>/project/<projectId>"
{url: pubUrl2}, // "/pub/<waId>/project/<projectId>"
{url: `${serviceUrl}/~tag`} // "/wa/<waId>/~tag"
];
/** @type {Array<Help4.widget.companionCore.SEN.ProjectResponses>} */
const response = await this.doMultifileRequest(config, {serverBaseUrl, request});
if (!response) return null;
/** @type {Help4.widget.companionCore.SEN.ContextResponse} */
let pubContext = response[0];
/** @type {Help4.widget.companionCore.SEN.XmlResponse} */
let headEntityXml = response[1];
/** @type {Help4.widget.companionCore.SEN.XmlResponse} */
let headProjectDpr = response[2];
/** @type {Help4.widget.companionCore.SEN.ProjectDataResponse} */
let headProject = response[3];
/** @type {Help4.widget.companionCore.SEN.ProjectDataResponse} */
let pubProject = response[4];
/** @type {Help4.widget.companionCore.SEN.TagResponse} */
let tagInfo = response[5];
/** @type {Help4.widget.companionCore.SEN.PubProjectResponse|null} */
pubContext = extractPubContextInfo(pubContext);
/** @type {Document|null} */
headEntityXml = headEntityXml?.status ? null : headEntityXml;
/** @type {Document|null} */
headProjectDpr = headProjectDpr?.status ? null : headProjectDpr;
/** @type {Help4.widget.help.project.SEN.Project|null} */
headProject = !headProject?.status && headProject?.response || null;
/** @type {Help4.widget.help.project.SEN.Project|null} */
pubProject = !pubProject?.status && pubProject?.response || null;
/** @type {Help4.widget.help.project.SEN.TagInfoList|null} */
tagInfo = !tagInfo?.status && tagInfo?.response?.resource || null;
/** @type {Help4.widget.companionCore.SEN.ProjectResponsePub|null} */
const pub = pubContext && pubProject
? {context: pubContext, project: pubProject}
: null;
/** @type {Help4.widget.companionCore.SEN.ProjectResponseHead|null} */
const head = headEntityXml && headProjectDpr && headProject
? {entityXml: headEntityXml, projectDpr: headProjectDpr, project: headProject}
: null;
/** @type {Help4.widget.companionCore.SEN.ProjectResponse} */
return {pub, head, tagInfo};
}
}
}
})();