new wco wwws compatible with new backeend
This commit is contained in:
@@ -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
334
wco/apx/apxgeminicli.js
Normal 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());
|
@@ -6,15 +6,16 @@ apx.apxauth.loadwco = async (id, ctx) => {
|
||||
// if (dayjs(apx.data.headers.xdays).diff(dayjs(), "hours") >= 24) apx.apxauth.checkisauth();
|
||||
//load main.mustache of the component
|
||||
//when wco-xxx change it run this function
|
||||
console.log(`Load wconame:apxauth apx.apxauth.loadwco with id:${id} and ctx: ${JSON.stringify(ctx)}`);
|
||||
console.log(
|
||||
`Load wconame:apxauth apx.apxauth.loadwco with id:${id} and ctx: ${JSON.stringify(
|
||||
ctx
|
||||
)}`
|
||||
);
|
||||
const tpldataname = `${apx.data.pagename}_${id}_apxauth`;
|
||||
const apxauthid = document.getElementById(id)
|
||||
const apxauthid = document.getElementById(id);
|
||||
const data = apx.apxauth.getdata(id, ctx);
|
||||
if (apxauthid.innerHTML.trim() === "") {
|
||||
apxauthid.innerHTML = Mustache.render(
|
||||
apx.data.tpl.apxauthmain,
|
||||
data
|
||||
);
|
||||
apxauthid.innerHTML = Mustache.render(apx.data.tpl.apxauthmain, data);
|
||||
}
|
||||
apxauthid.querySelector(`.screenaction`).innerHTML = Mustache.render(
|
||||
apx.data.tpl[`apxauthscreen${ctx.link}`],
|
||||
@@ -29,7 +30,10 @@ apx.apxauth.getdata = (id, ctx) => {
|
||||
data.id = id;
|
||||
data.xalias = apx.data.headers.xalias;
|
||||
data.xtribe = apx.data.headers.xtribe;
|
||||
data.emailssuport = apx.data.appdata.emailsupport;
|
||||
|
||||
data.emailssuport = apx.data.appdata.emailsupport
|
||||
? apx.data.appdata.emailsupport
|
||||
: "";
|
||||
switch (ctx.link) {
|
||||
case "logout":
|
||||
if (!data.profils) data.profils = [];
|
||||
@@ -55,34 +59,46 @@ apx.apxauth.getdata = (id, ctx) => {
|
||||
break;
|
||||
}
|
||||
console.log("data for tpl:", data);
|
||||
return data
|
||||
return data;
|
||||
};
|
||||
|
||||
apx.apxauth.redirecturlwithauth = (url, tribe, webapp, newwindow, windowname = '_blank') => {
|
||||
url = url.replace(/_[a-zA-Z0-9]{2}\.html/, `_${apx.data.headers.xlang}.html`)
|
||||
url += `?xtribe=${tribe}&xapp=${webapp}&xalias=${apx.data.headers.xalias}`
|
||||
url += `&xdays=${apx.data.headers.xdays}&xhash=${apx.data.headers.xhash}`
|
||||
url += `&xprofils=${apx.data.headers.xprofils.join(',')}`
|
||||
url += `&xtrkversion=${apx.data.headers.xtrkversion}&xuuid=${apx.data.headers.xuuid}`
|
||||
apx.apxauth.redirecturlwithauth = (
|
||||
url,
|
||||
tribe,
|
||||
webapp,
|
||||
newwindow,
|
||||
windowname = "_blank"
|
||||
) => {
|
||||
url = url.replace(/_[a-zA-Z0-9]{2}\.html/, `_${apx.data.headers.xlang}.html`);
|
||||
url += `?xtribe=${tribe}&xapp=${webapp}&xalias=${apx.data.headers.xalias}`;
|
||||
url += `&xdays=${apx.data.headers.xdays}&xhash=${apx.data.headers.xhash}`;
|
||||
url += `&xprofils=${apx.data.headers.xprofils.join(",")}`;
|
||||
url += `&xtrkversion=${apx.data.headers.xtrkversion}&xuuid=${apx.data.headers.xuuid}`;
|
||||
if (newwindow) {
|
||||
try {
|
||||
const newwin = window.open(url, windowname)
|
||||
if (newwin === null || typeof newwin === 'undefined') {
|
||||
console.warn("L'ouverture de la fenêtre a été bloquée par un bloqueur de pop-up.");
|
||||
const newwin = window.open(url, windowname);
|
||||
if (newwin === null || typeof newwin === "undefined") {
|
||||
console.warn(
|
||||
"L'ouverture de la fenêtre a été bloquée par un bloqueur de pop-up."
|
||||
);
|
||||
// Vous pouvez informer l'utilisateur ici qu'il doit désactiver son bloqueur de pop-up
|
||||
alert("Votre navigateur a bloqué l'ouverture d'un nouvel onglet. Veuillez autoriser les pop-ups pour ce site.");
|
||||
alert(
|
||||
"Votre navigateur a bloqué l'ouverture d'un nouvel onglet. Veuillez autoriser les pop-ups pour ce site."
|
||||
);
|
||||
} else {
|
||||
// Optionnel: Mettre le focus sur la nouvelle fenêtre/onglet
|
||||
newwin.focus();
|
||||
}
|
||||
return newwin;
|
||||
} catch (error) {
|
||||
console.error("Une erreur est survenue lors de l'ouverture de l'onglet :", error);
|
||||
console.error(
|
||||
"Une erreur est survenue lors de l'ouverture de l'onglet :",
|
||||
error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* logout
|
||||
@@ -148,7 +164,7 @@ apx.apxauth.setheadersauth = async (
|
||||
apx.data.headers.xalias = alias;
|
||||
apx.data.headers.xdays = dayjs().valueOf();
|
||||
const msg = `${alias}_${apx.data.headers.xdays}`;
|
||||
//console.log("pvk", privatekey);
|
||||
|
||||
try {
|
||||
apx.data.headers.xhash = await apx.apxauth.clearmsgSignature(
|
||||
publickey,
|
||||
@@ -185,7 +201,10 @@ apx.apxauth.authentifyme = async (
|
||||
//console.log(privatekey);
|
||||
//clean previous answer if exist
|
||||
|
||||
const idparent=document.getElementById(id).parentElement?.closest('[wco-name]').getAttribute('id')
|
||||
const idparent = document
|
||||
.getElementById(id)
|
||||
.parentElement?.closest("[wco-name]")
|
||||
.getAttribute("id");
|
||||
document.querySelector(`#${id} .msginfo`).innerHTML = "";
|
||||
if (alias.length < 3 || privatekey.length < 200) {
|
||||
apx.notification(`#${id} .msginfo`, {
|
||||
@@ -218,10 +237,13 @@ apx.apxauth.authentifyme = async (
|
||||
axios
|
||||
.get(`/api/apxtri/pagans/isauth`, {
|
||||
headers: apx.data.headers,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((rep) => {
|
||||
// Authenticate then store profils in header
|
||||
// remove xhash for security this xhaskl is stored from the server as cookie http only.
|
||||
apx.data.headers.xprofils = rep.data.data.xprofils;
|
||||
delete apx.data.headers.xhash;
|
||||
apx.save();
|
||||
// if this page is call with apxid_fr.html?url=httpsxxx then it redirect to this page.
|
||||
//alert(`${window.location.href.includes("/src/")?"/src/":""}${apx.pagecontext.hash.url}`)
|
||||
@@ -229,14 +251,18 @@ apx.apxauth.authentifyme = async (
|
||||
window.location.href = `${apx.pagecontext.hash.url}`;
|
||||
} else {
|
||||
//location.reload();
|
||||
document.getElementById(idparent).setAttribute('wco-link','mytribes');
|
||||
document
|
||||
.getElementById(idparent)
|
||||
.setAttribute("wco-link", "mytribes");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Not authentify:", err);
|
||||
delete apx.data.auth;
|
||||
apx.save();
|
||||
document.getElementById(idparent).setAttribute("wco-link", "signin")
|
||||
document
|
||||
.getElementById(idparent)
|
||||
.setAttribute("wco-link", "signin");
|
||||
if (err.response) {
|
||||
apx.notification(`#${id} .msginfo`, err.response.data);
|
||||
} else if (err.request) {
|
||||
@@ -486,13 +512,8 @@ apx.apxauth.authenticatedetachedSignature = async (
|
||||
return false;
|
||||
}
|
||||
};
|
||||
apx.apxauth.createIdentity = async (
|
||||
id,
|
||||
alias,
|
||||
recoemail,
|
||||
passphrase = ""
|
||||
) => {
|
||||
document.querySelector(`#${id} .msginfo`).innerHTML = ""
|
||||
apx.apxauth.createIdentity = async (id, alias, recoemail, passphrase = "") => {
|
||||
document.querySelector(`#${id} .msginfo`).innerHTML = "";
|
||||
const aliasregex = /^[a-z0-9]*$/;
|
||||
//console.log(aliasregex.test(alias));
|
||||
if (!(alias && alias.length > 3 && aliasregex.test(alias))) {
|
||||
@@ -542,9 +563,7 @@ apx.apxauth.createIdentity = async (
|
||||
//console.log(apx.data.tmpauth);
|
||||
["publickey", "privatekey"].forEach((k) => {
|
||||
console.log(`${id} button.signup${k}`);
|
||||
const btn = document.querySelector(
|
||||
`#${id} button.signup${k}`
|
||||
);
|
||||
const btn = document.querySelector(`#${id} button.signup${k}`);
|
||||
btn.addEventListener("click", () => {
|
||||
const blob = new Blob([keys[k]], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -561,12 +580,8 @@ apx.apxauth.createIdentity = async (
|
||||
`#${id} .signupalias, #${id} .signupemailrecovery, #${id} .signuppassphrase`
|
||||
)
|
||||
.forEach((e) => e.setAttribute("disabled", "disabled"));
|
||||
document
|
||||
.querySelector(`#${id} .getmykeys`)
|
||||
.classList.remove("hidden");
|
||||
document
|
||||
.querySelector(`#${id} .btncreatekey`)
|
||||
.classList.add("hidden");
|
||||
document.querySelector(`#${id} .getmykeys`).classList.remove("hidden");
|
||||
document.querySelector(`#${id} .btncreatekey`).classList.add("hidden");
|
||||
} else {
|
||||
apx.notification(
|
||||
`#${id} .msginfo`,
|
||||
@@ -617,8 +632,14 @@ apx.apxauth.registerIdentity = async (id, trustedtribe) => {
|
||||
const data = {};
|
||||
data.alias = apx.data.tmpauth.keys.alias;
|
||||
data.publickey = apx.data.tmpauth.keys.publickey;
|
||||
console.log(apx.data.tmpauth.recoemail, Checkjson.testformat(apx.data.tmpauth.recoemail, "email"))
|
||||
if (apx.data.tmpauth.recoemail && Checkjson.testformat(apx.data.tmpauth.recoemail, "email")) {
|
||||
console.log(
|
||||
apx.data.tmpauth.recoemail,
|
||||
Checkjson.testformat(apx.data.tmpauth.recoemail, "email")
|
||||
);
|
||||
if (
|
||||
apx.data.tmpauth.recoemail &&
|
||||
Checkjson.testformat(apx.data.tmpauth.recoemail, "email")
|
||||
) {
|
||||
data.passphrase = apx.data.tmpauth.keyspassphrase;
|
||||
data.privatekey = apx.data.tmpauth.keysprivatekey;
|
||||
data.email = apx.data.tmpauth.recoemail;
|
||||
@@ -629,10 +650,8 @@ apx.apxauth.registerIdentity = async (id, trustedtribe) => {
|
||||
.then((reppagan) => {
|
||||
//console.log(reppagan.data);
|
||||
apx.notification(`#${id} .msginfo`, reppagan.data);
|
||||
authid.querySelector(`.btncreateidentity`)
|
||||
.classList.add("hidden");
|
||||
authid.querySelector(`.signupbtnreload`)
|
||||
.classList.remove("hidden");
|
||||
authid.querySelector(`.btncreateidentity`).classList.add("hidden");
|
||||
authid.querySelector(`.signupbtnreload`).classList.remove("hidden");
|
||||
//remove tmp cause create phc change to keep tplauth in memory and avoid asking again the pasword
|
||||
//delete apx.data.tmpauth;
|
||||
//apx.save();
|
||||
|
347
wco/apxauth/apxauthgeminicli.js
Normal file
347
wco/apxauth/apxauthgeminicli.js
Normal file
@@ -0,0 +1,347 @@
|
||||
/* eslint-env browser */
|
||||
/* eslint-disable no-alert, no-console */
|
||||
|
||||
/**
|
||||
* @file apxauth.js (previously authnew.js)
|
||||
* @description Modern, class-based implementation for handling authentication (apxauth) components.
|
||||
* @version 2.1
|
||||
* @author support@ndda.fr
|
||||
*/
|
||||
|
||||
// Establish the global namespace
|
||||
window.apx = window.apx || {};
|
||||
|
||||
/**
|
||||
* @class ApxAuth
|
||||
* Manages authentication flows, including sign-in, sign-up, logout, and key management.
|
||||
*/
|
||||
class ApxAuth {
|
||||
constructor() {
|
||||
if (typeof apx.main === 'undefined') {
|
||||
throw new Error("ApxAuth requires a global 'apx.main' (ApxManager) instance.");
|
||||
}
|
||||
}
|
||||
|
||||
async loadwco(id, ctx) {
|
||||
console.log(`[apxauth] loadwco triggered for id: ${id} with context:`, ctx);
|
||||
const componentRoot = document.getElementById(id);
|
||||
if (!componentRoot) return;
|
||||
|
||||
const data = this._getData(id, ctx);
|
||||
|
||||
if (componentRoot.innerHTML.trim() === "") {
|
||||
componentRoot.innerHTML = Mustache.render(apx.main.data.tpl.apxauthmain, data);
|
||||
}
|
||||
|
||||
const screenContainer = componentRoot.querySelector('.screenaction');
|
||||
if (screenContainer) {
|
||||
const screenTemplate = apx.main.data.tpl[`apxauthscreen${ctx.link}`];
|
||||
if (screenTemplate) {
|
||||
screenContainer.innerHTML = Mustache.render(screenTemplate, data);
|
||||
}
|
||||
}
|
||||
|
||||
const msgInfo = componentRoot.querySelector('.msginfo');
|
||||
if (msgInfo) msgInfo.innerHTML = "";
|
||||
}
|
||||
|
||||
_getData(id, ctx) {
|
||||
const tpldataname = `${apx.main.data.pagename}_${id}_apxauth`;
|
||||
const data = JSON.parse(JSON.stringify(apx.main.data.tpldata[tpldataname] || {}));
|
||||
|
||||
data.id = id;
|
||||
data.xalias = apx.main.data.headers.xalias;
|
||||
data.xtribe = apx.main.data.headers.xtribe;
|
||||
data.emailsupport = apx.main.data.appdata?.emailsupport;
|
||||
|
||||
if (ctx.link === 'logout') {
|
||||
data.profils = apx.main.data.headers.xprofils
|
||||
.filter(p => !['anonymous', 'pagans', 'persons'].includes(p))
|
||||
.map(p => apx.main.data.options.profil.itms[p]?.title);
|
||||
data.noprofils = data.profils.length === 0;
|
||||
data.member = apx.main.data.headers.xprofils.includes('persons');
|
||||
data.websites = apx.main.data.appdata?.websites;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
redirectWithAuth(url, tribe, webapp, newWindow = false, windowName = '_blank') {
|
||||
const { xlang, xalias, xdays, xhash, xprofils, xtrkversion, xuuid } = apx.main.data.headers;
|
||||
let authUrl = url.replace(/_[a-z]{2}\.html/, `_${xlang}.html`);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
xtribe: tribe,
|
||||
xapp: webapp,
|
||||
xalias,
|
||||
xdays,
|
||||
xhash,
|
||||
xprofils: xprofils.join(','),
|
||||
xtrkversion,
|
||||
xuuid
|
||||
});
|
||||
|
||||
authUrl += `?${params.toString()}`;
|
||||
|
||||
if (newWindow) {
|
||||
try {
|
||||
const newWin = window.open(authUrl, windowName);
|
||||
if (!newWin) {
|
||||
alert("Popup blocked. Please allow popups for this site.");
|
||||
return null;
|
||||
}
|
||||
newWin.focus();
|
||||
return newWin;
|
||||
} catch (error) {
|
||||
console.error("Error opening new window:", error);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
window.location.href = authUrl;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
await axios.get('/api/apxtri/pagans/logout', { headers: apx.main.data.headers });
|
||||
} catch (err) {
|
||||
console.error("Logout API call failed:", err);
|
||||
}
|
||||
|
||||
apx.main.data = window.apxtri;
|
||||
apx.main.saveState();
|
||||
|
||||
if (apx.main.pageContext.hash.url) {
|
||||
window.location.href = apx.main.pageContext.hash.url;
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async _setAuthHeaders(alias, passphrase, publickey, privatekey, rememberme) {
|
||||
if (alias.length < 3 || publickey.length < 200) {
|
||||
return { status: 406, ref: "Pagans", msg: "aliasorprivkeytooshort" };
|
||||
}
|
||||
|
||||
if (rememberme) {
|
||||
apx.main.data.auth = { alias, publickey, privatekey, passphrase: passphrase || "" };
|
||||
} else {
|
||||
delete apx.main.data.auth;
|
||||
}
|
||||
|
||||
apx.main.data.headers.xalias = alias;
|
||||
apx.main.data.headers.xdays = dayjs().valueOf();
|
||||
const message = `${alias}_${apx.main.data.headers.xdays}`;
|
||||
|
||||
try {
|
||||
apx.main.data.headers.xhash = await this._clearMsgSignature(publickey, privatekey, passphrase, message);
|
||||
apx.main.saveState();
|
||||
return { status: 200 };
|
||||
} catch (err) {
|
||||
return { status: 500, ref: "Middlewares", msg: "unconsistentpgp", data: { err: err.message } };
|
||||
}
|
||||
}
|
||||
|
||||
async authenticate(id, alias, passphrase, privatekey, rememberme) {
|
||||
const msgContainer = `#${id} .msginfo`;
|
||||
apx.main.notify(msgContainer, {}, true);
|
||||
|
||||
if (alias.length < 3 || privatekey.length < 200) {
|
||||
apx.main.notify(msgContainer, { status: 406, ref: "Pagans", msg: "aliasorprivkeytooshort" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: { data: paganData } } = await axios.get(`/api/apxtri/pagans/alias/${alias}`, { headers: apx.main.data.headers });
|
||||
|
||||
const headersResult = await this._setAuthHeaders(alias, passphrase, paganData.publickey, privatekey, rememberme);
|
||||
if (headersResult.status !== 200) {
|
||||
apx.main.notify(msgContainer, headersResult);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: { data: authData } } = await axios.get('/api/apxtri/pagans/isauth', { headers: apx.main.data.headers, withCredentials: true });
|
||||
|
||||
apx.main.data.headers.xprofils = authData.xprofils;
|
||||
apx.main.saveState();
|
||||
|
||||
if (apx.main.pageContext.hash.url) {
|
||||
window.location.href = apx.main.pageContext.hash.url;
|
||||
} else {
|
||||
const parentWco = document.getElementById(id).closest('[wco-name]');
|
||||
if (parentWco) parentWco.setAttribute('wco-link', 'mytribes');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error("Authentication failed:", err);
|
||||
delete apx.main.data.auth;
|
||||
apx.main.saveState();
|
||||
const parentWco = document.getElementById(id).closest('[wco-name]');
|
||||
if (parentWco) parentWco.setAttribute('wco-link', 'signin');
|
||||
apx.main.notify(msgContainer, err.response?.data || { status: 500, ref: "Middlewares", msg: "errrequest" });
|
||||
}
|
||||
}
|
||||
|
||||
async recoverKey(id, aliasOrEmail) {
|
||||
const msgContainer = `#${id} .msginfo`;
|
||||
apx.main.notify(msgContainer, {}, true);
|
||||
|
||||
if (aliasOrEmail.length < 3) {
|
||||
apx.main.notify(msgContainer, { status: 406, ref: "Pagans", msg: "recoveryemailnotfound", data: { search: aliasOrEmail } });
|
||||
return;
|
||||
}
|
||||
|
||||
const recoveryData = {
|
||||
tribe: apx.main.data.headers.xtribe,
|
||||
search: aliasOrEmail,
|
||||
emailalias: Checkjson.testformat(aliasOrEmail, "email") ? "email" : "alias",
|
||||
};
|
||||
|
||||
try {
|
||||
const { data: response } = await axios.post('/api/apxtri/pagans/keyrecovery', recoveryData, { headers: apx.main.data.headers });
|
||||
response.data.search = aliasOrEmail;
|
||||
apx.main.notify(msgContainer, response, true);
|
||||
} catch (err) {
|
||||
const errorData = err.response?.data || { status: 500, ref: "Pagans", msg: "checkconsole" };
|
||||
errorData.data = { ...errorData.data, search: aliasOrEmail };
|
||||
apx.main.notify(msgContainer, errorData, true);
|
||||
}
|
||||
}
|
||||
|
||||
async _generateKey(alias, passphrase) {
|
||||
const { privateKey, publicKey } = await openpgp.generateKey({
|
||||
type: "ecc",
|
||||
curve: "curve25519",
|
||||
userIDs: [{ alias }],
|
||||
passphrase,
|
||||
format: "armored",
|
||||
});
|
||||
return { alias, privatekey: privateKey, publickey: publicKey };
|
||||
}
|
||||
|
||||
async createIdentity(id, alias, recoveryEmail, passphrase = "") {
|
||||
const msgContainer = `#${id} .msginfo`;
|
||||
apx.main.notify(msgContainer, {}, true);
|
||||
|
||||
const aliasRegex = /^[a-z0-9]{4,}$/;
|
||||
if (!aliasRegex.test(alias)) {
|
||||
apx.main.notify(msgContainer, { status: 406, ref: "Pagans", msg: "invalidalias" }, true);
|
||||
return;
|
||||
}
|
||||
if (recoveryEmail && !Checkjson.testformat(recoveryEmail, "email")) {
|
||||
apx.main.notify(msgContainer, { status: 406, ref: "Pagans", msg: "invalidemail" }, true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.get(`/api/apxtri/pagans/alias/${alias}`, { headers: apx.main.data.headers });
|
||||
apx.main.notify(msgContainer, { ref: "Pagans", msg: "aliasexist", data: { alias } }, true);
|
||||
} catch (err) {
|
||||
if (err.response?.status === 404) {
|
||||
const keys = await this._generateKey(alias, passphrase);
|
||||
apx.main.data.tmpauth = { keys, recoveryEmail, passphrase };
|
||||
this._showKeyDownloadUI(id, keys);
|
||||
} else {
|
||||
apx.main.notify(msgContainer, { ref: "Middlewares", msg: "errrequest" }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_showKeyDownloadUI(id, keys) {
|
||||
const componentRoot = document.getElementById(id);
|
||||
['publickey', 'privatekey'].forEach(keyType => {
|
||||
const btn = componentRoot.querySelector(`button.signup${keyType}`);
|
||||
if(btn) {
|
||||
btn.onclick = () => {
|
||||
const blob = new Blob([keys[keyType]], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${keys.alias}_${keyType}.txt`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
componentRoot.querySelectorAll('.signupalias, .signupemailrecovery, .signuppassphrase').forEach(el => el.disabled = true);
|
||||
componentRoot.querySelector('.getmykeys')?.classList.remove('hidden');
|
||||
componentRoot.querySelector('.btncreatekey')?.classList.add('hidden');
|
||||
}
|
||||
|
||||
async registerIdentity(id, isTrustedTribe) {
|
||||
const msgContainer = `#${id} .msginfo`;
|
||||
const { keys, recoveryEmail, passphrase } = apx.main.data.tmpauth;
|
||||
|
||||
const headersResult = await this._setAuthHeaders(keys.alias, passphrase, keys.publickey, keys.privatekey, false);
|
||||
if (headersResult.status !== 200) {
|
||||
apx.main.notify(msgContainer, headersResult);
|
||||
return;
|
||||
}
|
||||
|
||||
const registrationData = {
|
||||
alias: keys.alias,
|
||||
publickey: keys.publickey,
|
||||
trustedtribe: isTrustedTribe,
|
||||
};
|
||||
|
||||
if (recoveryEmail && Checkjson.testformat(recoveryEmail, "email")) {
|
||||
registrationData.email = recoveryEmail;
|
||||
registrationData.passphrase = passphrase;
|
||||
registrationData.privatekey = keys.privatekey;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: response } = await axios.post('/api/apxtri/pagans', registrationData, { headers: apx.main.data.headers });
|
||||
apx.main.notify(msgContainer, response);
|
||||
document.querySelector(`#${id} .btncreateidentity`)?.classList.add('hidden');
|
||||
document.querySelector(`#${id} .signupbtnreload`)?.classList.remove('hidden');
|
||||
} catch (err) {
|
||||
apx.main.notify(msgContainer, err.response?.data || { status: 500, ref: "Pagans", msg: "errcreate" });
|
||||
}
|
||||
}
|
||||
|
||||
async joinTribe(id) {
|
||||
const msgContainer = `#${id} .msginfo`;
|
||||
const personData = {
|
||||
alias: apx.main.data.headers.xalias,
|
||||
profils: [...new Set([...apx.main.data.headers.xprofils, 'persons'])],
|
||||
};
|
||||
|
||||
try {
|
||||
const { data: response } = await axios.put(`/api/apxtri/pagans/person/${apx.main.data.headers.xtribe}`, personData, { headers: apx.main.data.headers });
|
||||
apx.main.notify(msgContainer, response);
|
||||
await this.logout();
|
||||
|
||||
} catch (err) {
|
||||
apx.main.notify(msgContainer, err.response?.data || { status: 500, ref: "Pagans", msg: "errcreate" });
|
||||
}
|
||||
}
|
||||
|
||||
async _clearMsgSignature(pubK, privK, passphrase, message) {
|
||||
const publickey = await openpgp.readKey({ armoredKey: pubK });
|
||||
const privatekey = await openpgp.decryptKey({
|
||||
privateKey: await openpgp.readPrivateKey({ armoredKey: privK }),
|
||||
passphrase,
|
||||
});
|
||||
|
||||
const cleartextMessage = await openpgp.sign({
|
||||
message: await openpgp.createCleartextMessage({ text: message }),
|
||||
signingKeys: privatekey,
|
||||
});
|
||||
|
||||
const { signatures: [{ verified }] } = await openpgp.verify({
|
||||
message: await openpgp.readCleartextMessage({ cleartextMessage }),
|
||||
verificationKeys: publickey,
|
||||
});
|
||||
|
||||
if (!(await verified)) {
|
||||
throw new Error("Signature verification failed.");
|
||||
}
|
||||
return btoa(cleartextMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach an instance to the global namespace
|
||||
apx.apxauth = new ApxAuth();
|
228
wco/apxauthrefactor/apxauthrefactor.js
Normal file
228
wco/apxauthrefactor/apxauthrefactor.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* @file apxauthrefactor.js
|
||||
* @description Refactored WCO component for user authentication and identity management.
|
||||
* @version 2.0
|
||||
*/
|
||||
|
||||
((window) => {
|
||||
'use strict';
|
||||
|
||||
// --- Component Definition ---
|
||||
const apxauth = {};
|
||||
|
||||
// --- Private State & Configuration ---
|
||||
const _state = {
|
||||
container: null, // The main DOM element for this component
|
||||
config: {}, // Initial configuration
|
||||
templates: {}, // To cache loaded Mustache templates
|
||||
currentUser: {
|
||||
alias: 'anonymous',
|
||||
profils: ['anonymous'],
|
||||
// ... other user data
|
||||
},
|
||||
headers: { // To be sent with API requests
|
||||
xalias: 'anonymous',
|
||||
xdays: null,
|
||||
xhash: null,
|
||||
xprofils: ['anonymous'],
|
||||
xtribe: null,
|
||||
}
|
||||
};
|
||||
|
||||
// --- Private Helper Modules ---
|
||||
|
||||
/**
|
||||
* Logger utility for consistent console output.
|
||||
*/
|
||||
const _log = (level, ...args) => {
|
||||
console[level]('[apxauth]', ...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles all interactions with the backend API.
|
||||
*/
|
||||
const _api = {
|
||||
/**
|
||||
* Generic method to perform an API request.
|
||||
* @param {string} method - 'get', 'post', 'put', etc.
|
||||
* @param {string} endpoint - The API endpoint URL.
|
||||
* @param {object} [data=null] - The request payload for POST/PUT.
|
||||
* @returns {Promise<object>} - The response data.
|
||||
*/
|
||||
async request(method, endpoint, data = null) {
|
||||
try {
|
||||
const options = {
|
||||
method: method.toUpperCase(),
|
||||
headers: { ..._state.headers, 'Content-Type': 'application/json' },
|
||||
};
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
const response = await fetch(endpoint, options);
|
||||
const responseData = await response.json();
|
||||
if (!response.ok) {
|
||||
_log('error', `API Error on ${endpoint}:`, responseData);
|
||||
throw responseData; // Throw error object from backend
|
||||
}
|
||||
return responseData;
|
||||
} catch (error) {
|
||||
_log('error', `Request failed for ${endpoint}:`, error);
|
||||
throw error; // Re-throw to be caught by the caller
|
||||
}
|
||||
},
|
||||
|
||||
getAliasInfo: (alias) => _api.request('get', `/api/apxtri/pagans/alias/${alias}`),
|
||||
checkAuth: () => _api.request('get', '/api/apxtri/pagans/isauth'),
|
||||
logout: () => _api.request('get', '/api/apxtri/pagans/logout'),
|
||||
recoverKey: (data) => _api.request('post', '/api/apxtri/pagans/keyrecovery', data),
|
||||
registerIdentity: (data) => _api.request('post', '/api/apxtri/pagans', data),
|
||||
joinTribe: (tribe, data) => _api.request('put', `/api/apxtri/pagans/person/${tribe}`, data),
|
||||
};
|
||||
|
||||
/**
|
||||
* Encapsulates all OpenPGP-related functions.
|
||||
*/
|
||||
const _pgp = {
|
||||
async generateKey(alias, passphrase) {
|
||||
return openpgp.generateKey({
|
||||
type: 'ecc',
|
||||
curve: 'curve25519',
|
||||
userIDs: [{ alias }],
|
||||
passphrase,
|
||||
format: 'armored',
|
||||
});
|
||||
},
|
||||
|
||||
async createClearSignature(privateKeyArmored, passphrase, message) {
|
||||
const privateKey = await openpgp.decryptKey({
|
||||
privateKey: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored }),
|
||||
passphrase,
|
||||
});
|
||||
const cleartextMessage = await openpgp.sign({
|
||||
message: await openpgp.createCleartextMessage({ text: message }),
|
||||
signingKeys: privateKey,
|
||||
});
|
||||
return btoa(cleartextMessage); // Base64 encode for transport
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles rendering of Mustache templates.
|
||||
*/
|
||||
const _render = {
|
||||
/**
|
||||
* Renders a screen template into a specific area of the component.
|
||||
* @param {string} screenName - The name of the screen to render (e.g., 'signin', 'logout').
|
||||
* @param {object} [data={}] - Additional data to pass to the template.
|
||||
*/
|
||||
async screen(screenName, data = {}) {
|
||||
const target = _state.container.querySelector('.screenaction');
|
||||
if (!target) {
|
||||
_log('error', 'Render target ".screenaction" not found.');
|
||||
return;
|
||||
}
|
||||
const templateName = `apxauthscreen${screenName}`;
|
||||
if (!_state.templates[templateName]) {
|
||||
_log('error', `Template ${templateName} not found or loaded.`);
|
||||
return;
|
||||
}
|
||||
const renderData = { ..._state.currentUser, ..._state.config, ...data };
|
||||
target.innerHTML = Mustache.render(_state.templates[templateName], renderData);
|
||||
_log('log', `Rendered screen: ${screenName}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays a notification message.
|
||||
* @param {string} message - The message to display.
|
||||
* @param {'success'|'error'|'info'} type - The type of message.
|
||||
*/
|
||||
notification(message, type = 'error') {
|
||||
const target = _state.container.querySelector('.msginfo');
|
||||
if(target) {
|
||||
target.innerHTML = `<div class="notification ${type}">${message}</div>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --- Public API & Business Logic ---
|
||||
|
||||
/**
|
||||
* Sets the authentication headers required for API calls.
|
||||
* @private
|
||||
*/
|
||||
async function _setAuthHeaders(alias, publicKey, privateKey, passphrase) {
|
||||
_state.headers.xalias = alias;
|
||||
_state.headers.xdays = dayjs().valueOf();
|
||||
const message = `${alias}_${_state.headers.xdays}`;
|
||||
try {
|
||||
_state.headers.xhash = await _pgp.createClearSignature(privateKey, passphrase, message);
|
||||
return true;
|
||||
} catch (error) {
|
||||
_log('error', 'Failed to create signature:', error);
|
||||
_render.notification('Could not create a valid signature. Is the passphrase correct?', 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main authentication flow.
|
||||
*/
|
||||
apxauth.login = async (alias, passphrase, privateKey) => {
|
||||
try {
|
||||
const { data: { publickey } } = await _api.getAliasInfo(alias);
|
||||
|
||||
const headersSet = await _setAuthHeaders(alias, publickey, privateKey, passphrase);
|
||||
if (!headersSet) return;
|
||||
|
||||
const { data: authData } = await _api.checkAuth();
|
||||
|
||||
// Successfully authenticated
|
||||
_state.currentUser.alias = alias;
|
||||
_state.currentUser.profils = authData.xprofils;
|
||||
_state.headers.xprofils = authData.xprofils;
|
||||
|
||||
_log('log', 'Authentication successful. User:', _state.currentUser);
|
||||
|
||||
// TODO: Redirect or render the 'logout' or 'mytribes' screen
|
||||
_render.screen('logout'); // Example: render logout screen
|
||||
|
||||
} catch (error) {
|
||||
_log('error', 'Login failed:', error);
|
||||
_render.notification(error.msg || 'Login failed. Please check credentials.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the component.
|
||||
* @param {object} config - The component configuration.
|
||||
* @param {string} config.containerId - The ID of the DOM element to render into.
|
||||
* @param {object} config.templates - An object containing the pre-loaded Mustache templates.
|
||||
* @param {object} [config.initialData={}] - Any initial data to populate state.
|
||||
*/
|
||||
apxauth.init = (config) => {
|
||||
_state.container = document.getElementById(config.containerId);
|
||||
if (!_state.container) {
|
||||
_log('error', `Container with ID "${config.containerId}" not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
_state.config = config.initialData || {};
|
||||
_state.templates = config.templates || {};
|
||||
|
||||
// Render the main component layout
|
||||
_state.container.innerHTML = Mustache.render(_state.templates.apxauthmain, _state.config);
|
||||
|
||||
// Render the initial screen (e.g., 'signin' or 'logout' based on auth status)
|
||||
// For now, we default to 'signin'
|
||||
_render.screen('signin');
|
||||
|
||||
// TODO: Add event listeners using event delegation
|
||||
// _state.container.addEventListener('click', _handleEvents);
|
||||
|
||||
_log('log', 'apxauth component initialized.');
|
||||
};
|
||||
|
||||
// Expose the component to the global window object
|
||||
window.apxauth = apxauth;
|
||||
|
||||
})(window);
|
7
wco/apxauthrefactor/main_fr.mustache
Normal file
7
wco/apxauthrefactor/main_fr.mustache
Normal file
@@ -0,0 +1,7 @@
|
||||
<!-- screen action-->
|
||||
<div class="screenaction mt-5 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
</div>
|
||||
<!-- feedback action-->
|
||||
<div class="my-5">
|
||||
<p class="msginfo text-center text-info"></p>
|
||||
</div>
|
44
wco/apxauthrefactor/screenforgetkey_fr.mustache
Normal file
44
wco/apxauthrefactor/screenforgetkey_fr.mustache
Normal file
@@ -0,0 +1,44 @@
|
||||
<div class="mt-1">
|
||||
<label class="input validator mbt-1">
|
||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M11.89 4.111a5.5 5.5 0 1 0 0 7.778.75.75 0 1 1 1.06 1.061A7 7 0 1 1 15 8a2.5 2.5 0 0 1-4.083 1.935A3.5 3.5 0 1 1 11.5 8a1 1 0 0 0 2 0 5.48 5.48 0 0 0-1.61-3.889ZM10 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input id="inputaliasrecovery" type="text" placeholder="mail@site.com | alias" required />
|
||||
</label>
|
||||
<div class="validator-hint hidden">
|
||||
Enter a valid email or an alias (lowercase a-z and 0-9)
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<p>
|
||||
Si vous avez fait confiance à ce domaine pour garder vos clés, un email va être envoyé avec vos clés.
|
||||
</p>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<button
|
||||
class="btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="apx.apxauth.recoverykey('{{id}}',document.getElementById('inputaliasrecovery').value);"
|
||||
>
|
||||
M'envoyer un email avec mes clés
|
||||
</button>
|
||||
</div>
|
41
wco/apxauthrefactor/screeninformation_fr.mustache
Normal file
41
wco/apxauthrefactor/screeninformation_fr.mustache
Normal file
@@ -0,0 +1,41 @@
|
||||
<div class="space-y-6 text-justify">
|
||||
<h2>Qu'est-ce qu'une identité numérique décentralisée?</h2>
|
||||
<p>
|
||||
C'est <span class="text-secondary">un moyen de s'identifier en prouvant qu'on est le propriétaire
|
||||
d'un alias ou d'une clé publique</span>. Cette clé publique est accessible à tous et utilisée dans le
|
||||
monde numérique pour informer, payer, échanger,... et porte une
|
||||
réputation publique.
|
||||
</p>
|
||||
<p>
|
||||
Concrètement, c'est une paire de fichiers texte appelée clé publique
|
||||
et clé privée. La clé publique ne porte pas d'information
|
||||
personnelle autre que celles que vous avez bien voulu y associer.
|
||||
</p>
|
||||
<p>
|
||||
Une fonction mathématique permet au propriétaire de la clé privée de
|
||||
signer un message. Le destinataire dispose d'une autre fonction qui
|
||||
permet de vérifier que la signature a été faite avec la clé privée.
|
||||
</p>
|
||||
<p>
|
||||
Cette interface permet de créer une identité et de l'utiliser pour
|
||||
s'authentifier pour 24 heures. Elle n'envoie que le couple alias/clé
|
||||
publique sur internet, la clé privée est
|
||||
<span class="text-secondary">votre propriété et ne doit jamais être communiquée</span>. Si vous
|
||||
la perdez, vous ne pourrez plus récupérer les informations
|
||||
associées. Sauf si vous
|
||||
<span class="text-secondary">avez fait confiance à ce nom de domaine</span>, vous pourrez lui
|
||||
demander d'envoyer un email avec ces clés.
|
||||
</p>
|
||||
<p>
|
||||
Vous pouvez avoir autant d'identités que vous voulez, vous pouvez
|
||||
créer une identité pour des objets uniques. La seule limite est qu'à
|
||||
partir du moment où vous associez des informations personnelles à
|
||||
cette clé, le destinataire de ces informations peut les relier aux
|
||||
activités de cette identité inscrite dans la blockchain apxtri.
|
||||
</p>
|
||||
<p>
|
||||
Pour auditer le code js, utiliser l'outil de développement de votre
|
||||
navigateur. Pour toute remarque, question ou détection de failles :
|
||||
{{supportemail}}
|
||||
</p>
|
||||
</div>
|
39
wco/apxauthrefactor/screenlogout_fr.mustache
Normal file
39
wco/apxauthrefactor/screenlogout_fr.mustache
Normal file
@@ -0,0 +1,39 @@
|
||||
<div class="flex flex-col space-y-1 text-center">
|
||||
<div class="mt-1">
|
||||
<h1 class="mb-6">
|
||||
Bonjour {{xalias}},
|
||||
</h1>
|
||||
<p>
|
||||
Si cet appareil ne vous appartiens pas et que vous n'utilisez pas l'application, vous devriez vous deconnecter.
|
||||
</p>
|
||||
<p class="text-center text-gray-500">
|
||||
Nettoyer mes traces de cet appareil?
|
||||
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||
onclick="apx.apxauth.logout('{{id}}','logout','logout','apxauth')">Se deconnecter</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<p class="text-center text-gray-500">
|
||||
Voir mes échanges?
|
||||
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||
href="https://wall-ants.ndda.fr/apxwallet_fr.html" >Mon activité </a>
|
||||
</p>
|
||||
{{#member}}
|
||||
<p>
|
||||
Vous êtes membre de {{xtribe}} {{#noprofils}} sand profil particulier {{/noprofils}} {{^noprofils}}avec le(s) profil(s):<br><span class="text-info"> {{#profils}} {{.}}<br> {{/profils}} </span> {{/noprofils}}
|
||||
</p>
|
||||
{{/member}}
|
||||
{{^member}}
|
||||
<p> Vous n'êtes pas encore membre de {{xtribe}} </p>
|
||||
<p class=" mt-1 text-center text-gray-500">
|
||||
Envie d'jouter cette tribut {{xtribe}}?
|
||||
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||
onclick="apx.apxauth.jointribe('{{id}}')">Rejoindre {{xtribe}}</a>
|
||||
</p>
|
||||
{{/member}}
|
||||
<p>Les applications ou pages web de {{xtribe}} à visiter:<br>
|
||||
{{#websites}}<a class="font-semibold leading-6 text-secondary hover:text-primary" href='{{{href}}}'>{{{name}}}</a><br> {{/websites}}
|
||||
</p>
|
||||
<button class="btn btn-primary" onclick="apx.apxauth.runtest()">testbtn</button>
|
||||
</div>
|
||||
</div>
|
23
wco/apxauthrefactor/screenmytribes_fr.mustache
Normal file
23
wco/apxauthrefactor/screenmytribes_fr.mustache
Normal file
@@ -0,0 +1,23 @@
|
||||
<div class="flex flex-col space-y-1 text-center">
|
||||
<div class="mt-1">
|
||||
<h1 class="mb-6">
|
||||
Bonjour {{xalias}},
|
||||
</h1>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<p class="text-center text-gray-500">
|
||||
Redirige vers
|
||||
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||
onclick="apx.apxauth.redirecturlwithauth('http://recruiter.smatchit.newdev.ants/src/offer_fr.html','smatchit','recruiter',true);" >Redirige vers recruiter.smatchit.io/offer_fr.html&xhash....</a>
|
||||
</p>
|
||||
{{#member}}
|
||||
<p>
|
||||
Vous êtes membre de {{xtribe}} {{#noprofils}} sand profil particulier {{/noprofils}} {{^noprofils}}avec le(s) profil(s):<br><span class="text-info"> {{#profils}} {{.}}<br> {{/profils}} </span> {{/noprofils}}
|
||||
</p>
|
||||
{{/member}}
|
||||
<p>Les applications ou pages web de {{xtribe}} à visiter:<br>
|
||||
{{#websites}}<a class="font-semibold leading-6 text-secondary hover:text-primary" href='{{{href}}}'>{{{name}}}</a><br> {{/websites}}
|
||||
</p>
|
||||
<button class="btn btn-primary" onclick="apx.apxauth.runtest()">testbtn</button>
|
||||
</div>
|
||||
</div>
|
69
wco/apxauthrefactor/screensignin_fr.mustache
Normal file
69
wco/apxauthrefactor/screensignin_fr.mustache
Normal file
@@ -0,0 +1,69 @@
|
||||
<p data-wco="createid" class="text-center text-neutral-content">
|
||||
{{{signintitle}}}
|
||||
</p>
|
||||
<div class="mt-2">
|
||||
<label class="input validator">
|
||||
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input
|
||||
class="signinalias"
|
||||
type="input"
|
||||
required
|
||||
placeholder="alias"
|
||||
pattern="[a-z0-9\-]*"
|
||||
minlength="3"
|
||||
maxlength="30"
|
||||
title="{{{aliastitle}}}"
|
||||
/>
|
||||
</label>
|
||||
<p class="validator-hint hidden"> {{{aliasinvalid}}}</p>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label class="input mt-1">
|
||||
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input type="text" class="signinpassphrase" placeholder="passphrase (option)" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<textarea rows=5 class="mt-2 textarea signinprivatekey" placeholder="{{{privatekeyplaceholder}}}"></textarea>
|
||||
</div>
|
||||
<div class="flex m-6">
|
||||
<div class="w-14 flex-none">
|
||||
<input type="checkbox" checked="checked" class="checkbox signinrememberme" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-justify" >{{{remembermetext}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-4">
|
||||
<button
|
||||
class="btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="const loginid= document.getElementById('{{id}}');apx.apxauth.authentifyme(
|
||||
'{{id}}',
|
||||
loginid.querySelector('.signinalias').value,
|
||||
loginid.querySelector('.signinpassphrase').value,
|
||||
loginid.querySelector('.signinprivatekey').value,
|
||||
loginid.querySelector('.signinrememberme').checked
|
||||
)">
|
||||
{{{authentifyme}}}
|
||||
</button>
|
||||
</div>
|
121
wco/apxauthrefactor/screensignup_fr.mustache
Normal file
121
wco/apxauthrefactor/screensignup_fr.mustache
Normal file
@@ -0,0 +1,121 @@
|
||||
<p data-wco="createid" class="text-center text-neutral-content">
|
||||
{{{signuptitle}}}
|
||||
</p>
|
||||
<div class="paramid">
|
||||
<div class="mt-2">
|
||||
<label class="input validator mbt-1">
|
||||
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input
|
||||
class="signupalias"
|
||||
type="input"
|
||||
required
|
||||
placeholder="alias"
|
||||
pattern="[a-z0-9\-]*"
|
||||
minlength="3"
|
||||
maxlength="30"
|
||||
title="{{{aliastitle}}}"
|
||||
/>
|
||||
</label>
|
||||
<div class="validator-hint hidden">
|
||||
<p>{{{aliasinvalid}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label class="input validator mt-1">
|
||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input class="signupemailrecovery" type="email" placeholder="mail@site.com" required />
|
||||
</label>
|
||||
<div class="validator-hint hidden">
|
||||
{{{emailinvalid}}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label class="input mt-1">
|
||||
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input type="text" class="signuppassphrase" placeholder="passphrase (option)" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button
|
||||
class="btncreatekey btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="const authid=document.getElementById('{{id}}');console.log('{{id}}'); apx.apxauth.createIdentity(
|
||||
'{{id}}',
|
||||
authid.querySelector('.signupalias').value,
|
||||
authid.querySelector('.signupemailrecovery').value,
|
||||
authid.querySelector('.signuppassphrase').value
|
||||
)"
|
||||
>
|
||||
{{{createkey}}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="getmykeys hidden mt-1">
|
||||
<div class="flex m-6">
|
||||
<div class="w-14 flex-none">
|
||||
<input type="checkbox" checked="checked" class="signuptrustedcheck checkbox checkbox-secondary" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-justify" >{{{trusttext}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="downloadkeys text-center mt-1">
|
||||
<button
|
||||
class="signuppublickey btn btn-outline btn-accent text-white shadow-sm"
|
||||
>
|
||||
{{{downloadPuK}}}
|
||||
</button>
|
||||
<button
|
||||
class="signupprivatekey btn btn-outline btn-accent text-white shadow-sm"
|
||||
>
|
||||
{{{downloadPrK}}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button
|
||||
class="btncreateidentity btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="const authid=document.getElementById('{{id}}');apx.apxauth.registerIdentity(
|
||||
'{{id}}',
|
||||
authid.querySelector('.signuptrustedcheck').checked
|
||||
)"
|
||||
>{{{saveidentity}}}
|
||||
</button>
|
||||
<button
|
||||
class="signupbtnreload hidden btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="location.reload(true)"
|
||||
>
|
||||
{{{nextpage}}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -25,9 +25,9 @@
|
||||
"profil": "{{tribe}}/objects/options/profil"
|
||||
},
|
||||
"ref": {
|
||||
"Odmdb": "apxtri/objects/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/objects/tplstrings/Pagans",
|
||||
"Persons": "apxtri/objects/tplstrings/Persons"
|
||||
"Odmdb": "apxtri/models/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/models//tplstrings/Pagans",
|
||||
"Persons": "apxtri/models/tplstrings/Persons"
|
||||
},
|
||||
"schema": [
|
||||
"apxtri/objects/pagans",
|
||||
|
@@ -11,7 +11,7 @@
|
||||
"tpl": {},
|
||||
"tpldata": {},
|
||||
"ref": {
|
||||
"Checkjson": "apxtri/objects/tplstrings/Checkjson",
|
||||
"Notification": "apxtri/objects/tplstrings/Notifications", "Middlewares": "apxtri/objects/tplstrings/middlewares"
|
||||
"Checkjson": "apxtri/models/tplstrings/Checkjson",
|
||||
"Notification": "apxtri/models/tplstrings/Notifications", "Middlewares": "apxtri/models/tplstrings/Middlewares"
|
||||
}
|
||||
}
|
@@ -23,9 +23,9 @@
|
||||
"profil": "{{tribeId}}/objects/options/profil"
|
||||
},
|
||||
"ref": {
|
||||
"Odmdb": "apxtri/objects/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/objects/tplstrings/Pagans",
|
||||
"Persons": "apxtri/objects/tplstrings/Persons"
|
||||
"Odmdb": "apxtri/models/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/models/tplstrings/Pagans",
|
||||
"Persons": "apxtri/models/tplstrings/Persons"
|
||||
},
|
||||
"schema": ["apxtri/objects/pagans", "{{tribe}}/objects/persons"]
|
||||
}
|
||||
|
8
wco/privatri/main_fr.mustache
Normal file
8
wco/privatri/main_fr.mustache
Normal file
@@ -0,0 +1,8 @@
|
||||
<div id="privatri-container">
|
||||
<!-- Le contenu du composant privatri sera rendu ici -->
|
||||
<h2>Messages Privés (privatri)</h2>
|
||||
<p>Ce composant gère le stockage sécurisé des messages dans le navigateur.</p>
|
||||
<div id="privatri-threads">
|
||||
<!-- La liste des fils de discussion pourrait être affichée ici -->
|
||||
</div>
|
||||
</div>
|
194
wco/privatri/privatri.js
Normal file
194
wco/privatri/privatri.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* @file privatri.js
|
||||
* @description WCO component for managing private, thread-based messages in IndexedDB.
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
((window) => {
|
||||
'use strict';
|
||||
|
||||
// --- Component Definition ---
|
||||
const privatri = {};
|
||||
|
||||
// --- Private State ---
|
||||
let _db = null; // Holds the single, persistent IndexedDB connection
|
||||
const DB_NAME = 'privatriDB';
|
||||
const DB_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Logs messages to the console with a consistent prefix.
|
||||
* @param {string} level - The log level (e.g., 'log', 'error', 'warn').
|
||||
* @param {...any} args - The messages to log.
|
||||
*/
|
||||
const _log = (level, ...args) => {
|
||||
console[level]('[privatri]', ...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens and initializes the IndexedDB database.
|
||||
* This function is called by init() and handles the connection and schema upgrades.
|
||||
* @param {string[]} initialStoreNames - An array of store names (thread IDs) to ensure exist.
|
||||
* @returns {Promise<IDBDatabase>} A promise that resolves with the database connection.
|
||||
*/
|
||||
const _openDatabase = (initialStoreNames = []) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
_log('log', `Opening database "${DB_NAME}" with version ${DB_VERSION}.`);
|
||||
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
|
||||
// This event is only triggered for new databases or version changes.
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
_log('log', 'Database upgrade needed. Current stores:', [...db.objectStoreNames]);
|
||||
|
||||
initialStoreNames.forEach(storeName => {
|
||||
if (!db.objectStoreNames.contains(storeName)) {
|
||||
_log('log', `Creating new object store: "${storeName}"`);
|
||||
db.createObjectStore(storeName, { keyPath: 'key' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
_log('log', 'Database opened successfully.');
|
||||
_db = event.target.result;
|
||||
|
||||
// Generic error handler for the connection
|
||||
_db.onerror = (event) => {
|
||||
_log('error', 'Database error:', event.target.error);
|
||||
};
|
||||
|
||||
resolve(_db);
|
||||
};
|
||||
|
||||
request.onerror = (event) => {
|
||||
_log('error', 'Failed to open database:', event.target.error);
|
||||
reject(event.target.error);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// --- Public API ---
|
||||
|
||||
/**
|
||||
* Initializes the privatri component.
|
||||
* Opens the database connection and ensures initial object stores are created.
|
||||
* @param {object} config - Configuration object.
|
||||
* @param {string[]} [config.threads=[]] - An array of initial thread IDs (store names) to create.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
privatri.init = async (config = {}) => {
|
||||
if (_db) {
|
||||
_log('warn', 'privatri component already initialized.');
|
||||
return;
|
||||
}
|
||||
const threads = config.threads || [];
|
||||
await _openDatabase(threads);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a value from a specific store (thread).
|
||||
* @param {string} storeName - The name of the store (thread ID).
|
||||
* @param {string} key - The key of the item to retrieve.
|
||||
* @returns {Promise<any|null>} A promise that resolves with the value or null if not found.
|
||||
*/
|
||||
privatri.getValue = (storeName, key) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!_db || !_db.objectStoreNames.contains(storeName)) {
|
||||
_log('warn', `Store "${storeName}" does not exist.`);
|
||||
return resolve(null);
|
||||
}
|
||||
const transaction = _db.transaction(storeName, 'readonly');
|
||||
const store = transaction.objectStore(storeName);
|
||||
const request = store.get(key);
|
||||
|
||||
request.onsuccess = () => resolve(request.result ? request.result.value : null);
|
||||
request.onerror = (e) => {
|
||||
_log('error', `Error getting value for key "${key}" from store "${storeName}":`, e.target.error);
|
||||
reject(e.target.error);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds or updates a key-value pair in a specific store (thread).
|
||||
* @param {string} storeName - The name of the store (thread ID).
|
||||
* @param {string} key - The key of the item to set.
|
||||
* @param {any} value - The value to store.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
privatri.setValue = (storeName, key, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!_db || !_db.objectStoreNames.contains(storeName)) {
|
||||
_log('error', `Cannot set value. Store "${storeName}" does not exist.`);
|
||||
return reject(new Error(`Store "${storeName}" not found.`));
|
||||
}
|
||||
const transaction = _db.transaction(storeName, 'readwrite');
|
||||
const store = transaction.objectStore(storeName);
|
||||
const request = store.put({ key: key, value: value });
|
||||
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = (e) => {
|
||||
_log('error', `Error setting value for key "${key}" in store "${storeName}":`, e.target.error);
|
||||
reject(e.target.error);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a key-value pair from a specific store (thread).
|
||||
* @param {string} storeName - The name of the store (thread ID).
|
||||
* @param {string} key - The key of the item to remove.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
privatri.removeKey = (storeName, key) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!_db || !_db.objectStoreNames.contains(storeName)) {
|
||||
_log('warn', `Cannot remove key. Store "${storeName}" does not exist.`);
|
||||
return resolve(); // Resolve peacefully if store doesn't exist
|
||||
}
|
||||
const transaction = _db.transaction(storeName, 'readwrite');
|
||||
const store = transaction.objectStore(storeName);
|
||||
const request = store.delete(key);
|
||||
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = (e) => {
|
||||
_log('error', `Error removing key "${key}" from store "${storeName}":`, e.target.error);
|
||||
reject(e.target.error);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A utility function to save a batch of messages, demonstrating how to use the component.
|
||||
* @param {object} threadsObj - An object where keys are thread IDs and values are message objects.
|
||||
* @example
|
||||
* const messages = {
|
||||
* "thread-123": { "1678886400": { text: "Hello" } },
|
||||
* "thread-456": { "1678886401": { text: "Hi there" } }
|
||||
* };
|
||||
* await privatri.storeMessages(messages);
|
||||
*/
|
||||
privatri.storeMessages = async (threadsObj = {}) => {
|
||||
if (!_db) {
|
||||
_log('error', 'Database not initialized. Please call privatri.init() first.');
|
||||
return;
|
||||
}
|
||||
_log('log', 'Storing messages in IndexedDB...');
|
||||
for (const [uuid, threadObj] of Object.entries(threadsObj)) {
|
||||
// Ensure the object store exists before trying to write to it.
|
||||
if (!_db.objectStoreNames.contains(uuid)) {
|
||||
_log('warn', `Store "${uuid}" not found during message storage. You may need to re-init with this thread.`);
|
||||
continue;
|
||||
}
|
||||
for (const [timestamp, messageObj] of Object.entries(threadObj)) {
|
||||
await privatri.setValue(uuid, timestamp, messageObj);
|
||||
}
|
||||
}
|
||||
_log('log', 'Finished storing messages.');
|
||||
};
|
||||
|
||||
// Expose the component to the global window object
|
||||
window.privatri = privatri;
|
||||
|
||||
})(window);
|
131
wco/simplemobnav/simplemobnavgeminicli.js
Normal file
131
wco/simplemobnav/simplemobnavgeminicli.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/* eslint-env browser */
|
||||
/* eslint-disable no-alert, no-console */
|
||||
|
||||
/**
|
||||
* @file simplemobnav.js (previously simplemobnavnew.js)
|
||||
* @description Modern, class-based implementation for a simple mobile navigation component.
|
||||
* @version 2.1
|
||||
* @author support@ndda.fr
|
||||
*/
|
||||
|
||||
// Establish the global namespace
|
||||
window.apx = window.apx || {};
|
||||
|
||||
/**
|
||||
* @class SimpleMobNav
|
||||
* Manages a mobile navigation menu, dynamically rendering links based on user profiles.
|
||||
*/
|
||||
class SimpleMobNav {
|
||||
constructor() {
|
||||
if (typeof apx.main === 'undefined') {
|
||||
throw new Error("SimpleMobNav requires a global 'apx.main' (ApxManager) instance.");
|
||||
}
|
||||
}
|
||||
|
||||
loadwco(id, ctx) {
|
||||
console.log(`[simplemobnav] loadwco triggered for id: ${id} with context:`, ctx);
|
||||
const componentRoot = document.getElementById(id);
|
||||
if (!componentRoot) {
|
||||
console.error(`SimpleMobNav: Element with id "${id}" not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const tpldataname = `${apx.main.data.pagename}_${id}_simplemobnav`;
|
||||
if (!this._validateTplData(tpldataname)) return;
|
||||
|
||||
const tplData = apx.main.data.tpldata[tpldataname];
|
||||
let currentLink = ctx.link;
|
||||
|
||||
if (componentRoot.innerHTML.trim() === "") {
|
||||
currentLink = this._getInitialLink(tplData);
|
||||
tplData.contentscreen = currentLink;
|
||||
componentRoot.innerHTML = Mustache.render(apx.main.data.tpl.simplemobnavmain, tplData);
|
||||
} else {
|
||||
componentRoot.querySelectorAll('[wco-name]').forEach(el => {
|
||||
el.setAttribute('wco-link', currentLink);
|
||||
});
|
||||
}
|
||||
|
||||
this._renderNavLinks(id, currentLink, tplData);
|
||||
|
||||
const loadingIndicator = document.getElementById("loading");
|
||||
if (loadingIndicator) {
|
||||
loadingIndicator.classList.add("hidden");
|
||||
}
|
||||
console.log(`SimpleMobNav: Screen "${currentLink}" loaded.`);
|
||||
}
|
||||
|
||||
action(id, link, action, wconame) {
|
||||
if (action === "navigation") {
|
||||
document.getElementById(id)?.setAttribute("wco-link", link);
|
||||
return;
|
||||
}
|
||||
|
||||
const wco = window.apx[wconame];
|
||||
if (!wco) {
|
||||
console.warn(`Action target WCO "${wconame}" does not exist.`);
|
||||
return;
|
||||
}
|
||||
if (typeof wco[action] !== 'function') {
|
||||
console.warn(`Action "${action}" does not exist on WCO "${wconame}".`);
|
||||
return;
|
||||
}
|
||||
wco[action]();
|
||||
}
|
||||
|
||||
reload() {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
_renderNavLinks(id, currentLink, tplData) {
|
||||
const componentRoot = document.getElementById(id);
|
||||
const navlinkContainer = componentRoot?.querySelector('.navlink');
|
||||
if (!navlinkContainer) return;
|
||||
|
||||
const currentScreenConfig = tplData.links.find(m => m.link === currentLink);
|
||||
if (!currentScreenConfig) return;
|
||||
|
||||
const visibleLinks = tplData.links
|
||||
.filter(m =>
|
||||
currentScreenConfig.next.includes(m.link) &&
|
||||
m.allowedprofil.some(p => apx.main.data.headers.xprofils.includes(p))
|
||||
)
|
||||
.map(m => ({
|
||||
...m,
|
||||
classnavbutton: m.classnavbutton || tplData.classnavbutton,
|
||||
classnavlist: m.classnavlist || tplData.classnavlist,
|
||||
}));
|
||||
|
||||
const navTemplate = apx.main.data.tpl[`simplemobnav${tplData.navtpl}`];
|
||||
navlinkContainer.innerHTML = Mustache.render(navTemplate, { id, links: visibleLinks });
|
||||
}
|
||||
|
||||
_getInitialLink(tplData) {
|
||||
for (const menu of tplData.profilmenu) {
|
||||
if (apx.main.data.headers.xprofils.includes(menu.mainprofil)) {
|
||||
return menu.link;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_validateTplData(tpldataname) {
|
||||
const tplData = apx.main.data.tpldata[tpldataname];
|
||||
if (!tplData) {
|
||||
console.error(`Template data "${tpldataname}" not found in apx.main.data.tpldata.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const mandatoryProps = ["contentwconame", "contentid", "profilmenu", "links"];
|
||||
const missingProps = mandatoryProps.filter(p => !tplData[p]);
|
||||
|
||||
if (missingProps.length > 0) {
|
||||
console.error(`Missing properties in ${tpldataname}: ${missingProps.join(', ')}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Attach an instance to the global namespace
|
||||
apx.simplemobnav = new SimpleMobNav();
|
@@ -44,6 +44,7 @@
|
||||
<script src="/apxtrilib/dayjs/dayjs.min.js"></script>
|
||||
<script src="/apxtrilib/openpgp/dist/openpgp.min.js"></script>
|
||||
<script src="/apxtrilib/mustache/mustache.min.js"></script>
|
||||
<script src="/apxtrilib/qr-code-styling/lib/qr-code-styling.js"></script>
|
||||
<script src="/apxtrilib/checkjson.js"></script>
|
||||
|
||||
<script src="/api/apxtri/wwws/getwco/apx.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=apxid&code=enjoy"></script>
|
||||
|
@@ -1,8 +1,3 @@
|
||||
@source "../../../../../../../apxtri/objects/wco/apx/*.{html,js,mustache}";
|
||||
@source "../../../../../../../apxtri/objects/wwws/admin/src/**/*.{html,js,mustache}";
|
||||
@source "../../../../../../../apxtri/objects/wco/simplemobnav/*.{html,js,mustache}";
|
||||
@source "../../../../../../../apxtri/objects/wco/apxauth/*.{html,js,mustache}";
|
||||
@source "../../../../../../../apxtri/objects/wco/adminskull/*.{html,js,mustache}";
|
||||
@source "/media/phil/usbfarm/apxtowns/data/apxtri/objects/wco/apx/*.{html,js,mustache}";
|
||||
@source "/media/phil/usbfarm/apxtowns/data/apxtri/objects/wwws/admin/src/**/*.{html,js,mustache}";
|
||||
@source "/media/phil/usbfarm/apxtowns/data/apxtri/objects/wco/simplemobnav/*.{html,js,mustache}";
|
||||
|
@@ -1,22 +1,26 @@
|
||||
[
|
||||
{
|
||||
"pathsrc":"nodePath/apxtri/models/Checkjson.js",
|
||||
"pathdest":"dataPath/apxtri/objects/wwws/cdn/lib/checkjson.js"
|
||||
"pathsrc": "nodePath/apxtri/models/Checkjson.js",
|
||||
"pathdest": "dataPath/apxtri/objects/wwws/cdn/lib/checkjson.js"
|
||||
},
|
||||
{
|
||||
"pathsrc":"nodePath/apxtri/node_modules/axios/dist/axios.min.js",
|
||||
"pathdest":"dataPath/apxtri/objects/wwws/cdn/lib/axios/dist/axios.min.js"
|
||||
"pathsrc": "nodePath/apxtri/node_modules/axios/dist/axios.min.js",
|
||||
"pathdest": "dataPath/apxtri/objects/wwws/cdn/lib/axios/dist/axios.min.js"
|
||||
},
|
||||
{
|
||||
"pathsrc":"nodePath/apxtri/node_modules/dayjs/dayjs.min.js",
|
||||
"pathdest":"dataPath/apxtri/objects/wwws/cdn/lib/dayjs/dayjs.min.js"
|
||||
{
|
||||
"pathsrc": "nodePath/apxtri/node_modules/dayjs/dayjs.min.js",
|
||||
"pathdest": "dataPath/apxtri/objects/wwws/cdn/lib/dayjs/dayjs.min.js"
|
||||
},
|
||||
{
|
||||
"pathsrc":"nodePath/apxtri/node_modules/mustache/mustache.min.js",
|
||||
"pathdest":"dataPath/apxtri/objects/wwws/cdn/lib/mustache/mustache.min.js"
|
||||
{
|
||||
"pathsrc": "nodePath/apxtri/node_modules/mustache/mustache.min.js",
|
||||
"pathdest": "dataPath/apxtri/objects/wwws/cdn/lib/mustache/mustache.min.js"
|
||||
},
|
||||
{
|
||||
"pathsrc":"nodePath/apxtri/node_modules/openpgp/dist/openpgp.min.js",
|
||||
"pathdest":"dataPath/apxtri/objects/wwws/cdn/lib/openpgp/dist/openpgp.min.js"
|
||||
{
|
||||
"pathsrc": "nodePath/apxtri/node_modules/openpgp/dist/openpgp.min.js",
|
||||
"pathdest": "dataPath/apxtri/objects/wwws/cdn/lib/openpgp/dist/openpgp.min.js"
|
||||
},
|
||||
{
|
||||
"pathsrc": "nodePath/apxtri/node_modules/qr-code-styling/lib/qr-code-styling.js",
|
||||
"pathdest": "dataPath/apxtri/objects/wwws/cdn/lib/qr-code-styling/lib/qr-code-styling.js"
|
||||
}
|
||||
]
|
@@ -45,11 +45,11 @@
|
||||
"tpldata": {},
|
||||
"itms": {},
|
||||
"ref": {
|
||||
"Checkjson": "apxtri/objects/tplstrings/Checkjson",
|
||||
"Notification": "apxtri/objects/tplstrings/Notifications",
|
||||
"Odmdb": "apxtri/objects/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/objects/tplstrings/Pagans",
|
||||
"Middlewares": "apxtri/objects/tplstrings/middlewares"
|
||||
"Checkjson": "apxtri/models/tplstrings/Checkjson",
|
||||
"Notification": "apxtri/models/tplstrings/Notifications",
|
||||
"Odmdb": "apxtri/models/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/models/tplstrings/Pagans",
|
||||
"Middlewares": "apxtri/models/tplstrings/middlewares"
|
||||
},
|
||||
"schema": [
|
||||
"apxtri/objects/pagans",
|
||||
@@ -70,7 +70,9 @@
|
||||
"textContent": "L'Unique et sa propriété"
|
||||
}
|
||||
},
|
||||
"appdata": {}
|
||||
"appdata": {
|
||||
"emailsupport": "support@need-data.com"
|
||||
}
|
||||
},
|
||||
"apxid": {
|
||||
"version": 1,
|
||||
@@ -101,19 +103,19 @@
|
||||
},
|
||||
"itms": {},
|
||||
"ref": {
|
||||
"Checkjson": "apxtri/objects/tplstrings/Checkjson",
|
||||
"Notification": "apxtri/objects/tplstrings/Notifications",
|
||||
"Odmdb": "apxtri/objects/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/objects/tplstrings/Pagans",
|
||||
"Middlewares": "apxtri/objects/tplstrings/middlewares",
|
||||
"Persons": "apxtri/objects/tplstrings/Persons"
|
||||
"Checkjson": "apxtri/models/tplstrings/Checkjson",
|
||||
"Middlewares": "apxtri/models/tplstrings/Middlewares",
|
||||
"Notification": "apxtri/models/tplstrings/Notifications",
|
||||
"Odmdb": "apxtri/models/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/models/tplstrings/Pagans",
|
||||
"Persons": "apxtri/models/tplstrings/Persons"
|
||||
},
|
||||
"schema": [
|
||||
"apxtri/objects/pagans",
|
||||
"apxtri/objects/persons"
|
||||
],
|
||||
"options": {
|
||||
"profil": "apxtri/objects/options/profil"
|
||||
"profil": "apxtri/objects/options/itm/profil"
|
||||
},
|
||||
"wcodata": {
|
||||
"favicon": {
|
||||
@@ -168,12 +170,12 @@
|
||||
},
|
||||
"itms": {},
|
||||
"ref": {
|
||||
"Checkjson": "apxtri/objects/tplstrings/Checkjson",
|
||||
"Notification": "apxtri/objects/tplstrings/Notifications",
|
||||
"Odmdb": "apxtri/objects/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/objects/tplstrings/Pagans",
|
||||
"Middlewares": "apxtri/objects/tplstrings/middlewares",
|
||||
"Persons": "apxtri/objects/tplstrings/Persons"
|
||||
"Checkjson": "apxtri/models/tplstrings/Checkjson",
|
||||
"Notification": "apxtri/models/tplstrings/Notifications",
|
||||
"Odmdb": "apxtri/models/tplstrings/Odmdb",
|
||||
"Pagans": "apxtri/models/tplstrings/Pagans",
|
||||
"Middlewares": "apxtri/models/tplstrings/middlewares",
|
||||
"Persons": "apxtri/models/tplstrings/Persons"
|
||||
},
|
||||
"schema": [
|
||||
"apxtri/objects/pagans",
|
||||
|
1
wwws/recruiter/src/index_fr.html
Normal file
1
wwws/recruiter/src/index_fr.html
Normal file
@@ -0,0 +1 @@
|
||||
<h2>Test</h2>
|
Reference in New Issue
Block a user