new wco wwws compatible with new backeend

This commit is contained in:
2025-07-23 12:36:48 +02:00
parent bc76840707
commit b6ae865332
23 changed files with 1702 additions and 94 deletions

View File

@@ -484,7 +484,7 @@ apx.update = async () => {
//try again in 30 seconds
setTimeout(apx.update, 30000);
}
if (initset.data.msg == "datamodelupdate") {
if (initset.data.msg == "data_model_update") {
// mise à jour local
/*if (initset.data.data.wco) {

334
wco/apx/apxgeminicli.js Normal file
View File

@@ -0,0 +1,334 @@
/* eslint-env browser */
/* eslint-disable no-alert, no-console */
/**
* @file apx.js (previously apxnew.js)
* @description Modern, class-based implementation to manage data and interactions with an apxtri instance from a webpage.
* @version 2.1
* @author support@ndda.fr
*/
// Establish the global namespace
window.apx = window.apx || {};
/**
* @class ApxManager
* Manages the core application state and lifecycle.
*/
class ApxManager {
constructor() {
this.data = {};
this.pageContext = { search: {}, hash: {} };
this.afterUpdateCallbacks = [];
this.wcoProxies = {};
// Capture the observer flag immediately from the initial global config.
this.useWcoObserver = window.apxtri?.wcoobserver || false;
}
ready(callback) {
if (typeof callback !== 'function') {
alert("Apx.ready(callback) requires a valid function.");
return;
}
if (document.readyState !== 'loading') {
callback();
} else {
document.addEventListener('DOMContentLoaded', callback);
}
}
registerUpdateCallback(callback) {
this.afterUpdateCallbacks.push(callback);
}
lazyLoad() {
const { xalias, xuuid, xtrkversion, xlang } = this.data.headers;
const consentCookie = localStorage.getItem('consentcookie');
document.querySelectorAll('img[data-lazysrc]').forEach(img => {
let src = img.dataset.lazysrc;
if (img.dataset.trksrckey) {
src = `/trk/${src}?alias=${xalias}&uuid=${xuuid}&srckey=${img.dataset.trksrckey}&version=${xtrkversion}&consentcookie=${consentCookie}&lg=${xlang}`;
}
img.setAttribute('src', src);
img.removeAttribute('data-lazysrc');
});
document.querySelectorAll('[data-lazybgsrc]').forEach(el => {
el.style.backgroundImage = `url(${el.dataset.lazybgsrc})`;
el.removeAttribute('data-lazybgsrc');
});
document.querySelectorAll('a[data-trksrckey]').forEach(a => {
let urlDest = a.getAttribute('href');
if (!urlDest.startsWith('http') && !urlDest.startsWith('/')) {
urlDest = `/${urlDest}`;
}
const trackedHref = `/trk/redirect?alias=${xalias}&uuid=${xuuid}&srckey=${a.dataset.trksrckey}&version=${xtrkversion}&consentcookie=${consentCookie}&lg=${xlang}&url=${encodeURIComponent(urlDest)}`;
a.setAttribute('href', trackedHref);
a.removeAttribute('data-trksrckey');
});
}
notify(selector, responseData, clearBefore = false) {
const el = document.querySelector(selector);
if (!el) {
console.warn(`Notification selector not found: ${selector}`);
return;
}
if (clearBefore) {
el.innerHTML = '';
}
const messages = responseData.multimsg || [responseData];
messages.forEach(info => {
const template = this.data.ref?.[info.ref]?.[info.msg];
if (!template) {
console.warn(`Notification template not found for ref: ${info.ref}, msg: ${info.msg}`);
return;
}
el.innerHTML += Mustache.render(template, info.data);
});
if (responseData.status === 200) {
el.classList.remove('text-red');
el.classList.add('text-green');
} else {
el.classList.add('text-red');
el.classList.remove('text-green');
}
}
listenWcoData() {
if (!this.data.wco || Object.keys(this.data.wco).length === 0) return;
const updateElement = (element, value) => {
if (value.innerHTML !== undefined) element.innerHTML = value.innerHTML;
if (value.textContent !== undefined) element.textContent = value.textContent;
['src', 'alt', 'placeholder', 'class', 'href'].forEach(attr => {
if (value[attr] !== undefined) element.setAttribute(attr, value[attr]);
});
};
for (const prop in this.data.wco) {
if (Object.hasOwnProperty.call(this.data.wco, prop) && !this.wcoProxies[prop]) {
const elements = document.querySelectorAll(`[data-wco='${prop}']`);
elements.forEach(el => updateElement(el, this.data.wco[prop]));
this.wcoProxies[prop] = new Proxy(this.data.wco[prop], {
set: (target, key, value) => {
target[key] = value;
elements.forEach(el => updateElement(el, target));
return true;
},
});
}
}
this.wco = new Proxy(this.data.wco, {
set: (target, prop, value) => {
target[prop] = value;
this.listenWcoData();
return true;
}
});
}
startWcoObserver() {
console.log('[APX] Starting WCO observer and triggering initial load...');
const processElement = (element) => {
if (element.nodeType !== 1 || !element.hasAttribute('wco-name') || !element.id) {
return;
}
const wcoName = element.getAttribute('wco-name');
const wcoModule = window.apx?.[wcoName];
if (wcoModule && typeof wcoModule.loadwco === 'function') {
console.log(`[APX] Observer is processing component: ${wcoName} on #${element.id}`);
const ctx = {};
for (const attr of element.attributes) {
if (attr.name.startsWith('wco-')) {
ctx[attr.name.slice(4)] = attr.value;
}
}
wcoModule.loadwco(element.id, ctx);
} else {
console.warn(`[APX] WCO handler not found for component: apx.${wcoName}`);
}
};
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(processElement);
}
if (mutation.type === 'attributes' && mutation.attributeName.startsWith('wco-')) {
processElement(mutation.target);
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
});
// This is the key part from your original logic:
// Manually trigger the observer for components already in the DOM.
document.querySelectorAll('div[wco-name]').forEach((element) => {
const wcoName = element.getAttribute('wco-name');
console.log(`[APX] Manually triggering initial load for: ${wcoName}`);
element.setAttribute('wco-name', wcoName);
});
}
saveState() {
localStorage.setItem(this.data.headers.xapp, JSON.stringify(this.data));
}
parseUrl() {
const parse = (str) => str.slice(1).split('&').reduce((acc, kv) => {
if (kv) {
const [key, value] = kv.split('=');
acc[key] = decodeURIComponent(value);
}
return acc;
}, {});
this.pageContext.search = parse(window.location.search);
this.pageContext.hash = parse(window.location.hash);
}
loadState() {
if (typeof apxtri === 'undefined') {
throw new Error('Global `apxtri` configuration object is missing.');
}
const storedState = localStorage.getItem(apxtri.headers.xapp);
if (storedState) {
this.data = JSON.parse(storedState);
// Invalidate cache if key configuration has changed
if (
this.data.headers.xtribe !== apxtri.headers.xtribe ||
this.data.headers.xlang !== apxtri.headers.xlang ||
this.data.headers.xtrkversion !== apxtri.headers.xtrkversion
) {
localStorage.removeItem(apxtri.headers.xapp);
// Create a deep copy to prevent modifying the global apxtri object
this.data = JSON.parse(JSON.stringify(apxtri));
}
} else {
// Create a deep copy to prevent modifying the global apxtri object
this.data = JSON.parse(JSON.stringify(apxtri));
}
// Always update with current page info from the original config
this.data.pagename = apxtri.pagename;
if (apxtri.pageauth) this.data.pageauth = apxtri.pageauth;
}
handleAuthRedirect() {
const { xhash, xdays, xprofils, xtribe, url } = this.pageContext.hash;
if (xhash && xdays && xprofils && xtribe && dayjs(xdays).isValid() && dayjs(xdays).diff(dayjs(), 'hours') < 25) {
const headerKeys = ['xalias', 'xhash', 'xdays', 'xprofils', 'xtribe', 'xlang'];
const newHeaders = {};
let isValid = true;
headerKeys.forEach(key => {
if (this.pageContext.hash[key]) {
newHeaders[key] = (key === 'xprofils') ? this.pageContext.hash[key].split(',') : this.pageContext.hash[key];
} else {
isValid = false;
}
});
if (isValid) {
this.data.headers = { ...this.data.headers, ...newHeaders };
this.saveState();
if (url) {
window.location.href = url;
return true;
}
}
}
return false;
}
checkAccess() {
const { allowedprofils, pagename, pageauth, headers } = this.data;
if (allowedprofils && !allowedprofils.includes('anonymous') && pagename !== pageauth) {
const hasAccess = allowedprofils.some(p => headers.xprofils?.includes(p));
if (!hasAccess) {
alert(this.data.ref?.Middlewares?.notallowtoaccess || 'Access denied.');
return false;
}
if (dayjs().valueOf() - headers.xdays > 86400000) {
const redirectUrl = `/${pageauth}_${headers.xlang}.html#url=${pagename}_${headers.xlang}.html`;
document.location.href = redirectUrl;
return false;
}
}
return true;
}
async fetchAndUpdateDataModel() {
this.data.version = 0;
const { xalias, xtribe, xapp } = this.data.headers;
const ano = (xalias === 'anonymous') ? 'anonymous' : '';
const url = `/api/apxtri/wwws/updatelocaldb${ano}/${xtribe}/${xapp}/${this.data.pagename}/${this.data.version}`;
try {
const response = await axios.get(url, { headers: this.data.headers, timeout: 2000 });
if (response.data.msg === 'datamodelupdate') {
Object.keys(response.data.data).forEach(key => {
if (key !== 'headers') {
this.data[key] = response.data.data[key];
}
});
this.saveState();
console.log('Local data model updated.');
} else if (response.data.msg === 'forbidenaccess') {
alert(this.data.ref?.Middlewares?.notallowtoaccess || 'Access denied by API.');
return false;
}
} catch (error) {
console.error('API is unavailable. Retrying in 30 seconds.', error);
setTimeout(() => this.init(), 30000);
return false;
}
return true;
}
async init() {
this.loadState();
this.parseUrl();
if (this.handleAuthRedirect()) return;
if (!this.checkAccess()) return;
if (await this.fetchAndUpdateDataModel()) {
this.listenWcoData();
// This is the correct place to check and start the observer.
if (window.apxtri?.wcoobserver) {
this.startWcoObserver();
} else {
console.log('[APX] wcoobserver is not enabled.');
}
this.afterUpdateCallbacks.forEach(cb => cb());
this.lazyLoad();
this.saveState();
}
}
}
// Instantiate the manager and attach it to the global namespace
apx.main = new ApxManager();
// Start the application lifecycle
apx.main.ready(() => apx.main.init());