@constl/mandataire
Version:
Un mandataire très léger pour le client Constellation.
258 lines • 9.64 kB
JavaScript
import { TypedEmitter } from "tiny-typed-emitter";
import { v4 as uuidv4 } from "uuid";
import { ERREUR_EXÉCUTION_IPA, ERREUR_FONCTION_MANQUANTE, ERREUR_FORMAT_ARGUMENTS, ERREUR_INIT_IPA, ERREUR_INIT_IPA_DÉJÀ_LANCÉ, ERREUR_MESSAGE_INCONNU, ERREUR_MULTIPLES_FONCTIONS, ERREUR_PAS_UNE_FONCTION, } from "./codes.js";
import { lorsque } from "./utils.js";
class Callable extends Function {
// Code obtenu de https://hackernoon.com/creating-callable-objects-in-javascript-d21l3te1
//@ts-expect-error On ne peut pas appeller super() d'une fonction dans un contexte navigateur sécuritaire
constructor() {
const closure = function () {
// Rien faire. Je ne comprends pas tout ça mais ça fonctionne.
};
return Object.setPrototypeOf(closure, new.target.prototype);
}
}
export class Mandatairifiable extends Callable {
dernièreErreur;
événements;
événementsInternes;
tâches;
constructor() {
super();
this.événements = new TypedEmitter();
this.événementsInternes = new TypedEmitter();
this.tâches = {};
}
__call__(fonction, args = {}) {
if (typeof args !== "object")
this.erreur({
code: ERREUR_FORMAT_ARGUMENTS,
erreur: `La fonction ${fonction.join(".")} fut appelée avec arguments ${args}.
Toute fonction mandataire Constellation doit être appelée avec un seul argument en format d'objet (dictionnaire).`,
});
const idRequête = uuidv4();
const nomArgFonction = Object.entries(args).find((x) => typeof x[1] === "function")?.[0];
if (nomArgFonction) {
return this.appelerFonctionSuivre(idRequête, fonction, args, nomArgFonction);
}
else {
return this.appelerFonctionAction(idRequête, fonction, args);
}
}
async appelerFonctionSuivre(idRequête, fonction, args, nomArgFonction) {
const f = args[nomArgFonction];
const argsSansF = Object.fromEntries(Object.entries(args).filter((x) => typeof x[1] !== "function"));
// Vérifier format paramètres
if (f === undefined) {
this.erreur({
code: ERREUR_FONCTION_MANQUANTE,
erreur: "Aucun argument de nom " +
nomArgFonction +
" n'a été donnée pour " +
fonction.join("."),
idRequête,
});
}
if (Object.keys(args).length > Object.keys(argsSansF).length + 1) {
this.erreur({
code: ERREUR_MULTIPLES_FONCTIONS,
erreur: "Plus d'un argument pour " +
fonction.join(".") +
" est une fonction : " +
JSON.stringify(args),
idRequête,
});
}
else if (typeof f !== "function") {
this.erreur({
code: ERREUR_PAS_UNE_FONCTION,
erreur: "Argument " + nomArgFonction + "n'est pas une fonction : ",
idRequête,
});
}
const message = {
type: "suivre",
idRequête,
fonction,
args: argsSansF,
nomArgFonction,
};
const fRetour = async (fonction, args) => {
const idRetour = uuidv4();
const messageRetour = {
type: "retour",
idRequête,
fonction,
idRetour,
args,
};
const lorsqueRetour = lorsque(this.événementsInternes, idRetour);
this.envoyerMessageÀIpa(messageRetour);
const retour = await lorsqueRetour;
if (retour.type === "erreur") {
this.erreur({
erreur: retour.erreur,
idRequête,
code: retour.codeErreur || ERREUR_EXÉCUTION_IPA,
});
}
if (retour.type !== "confirmation") {
this.erreur({
erreur: `Type de retour ${JSON.stringify(retour)} non reconnu.`,
idRequête,
code: ERREUR_EXÉCUTION_IPA,
});
}
};
const tâche = {
idRequête,
fSuivre: f,
fRetour,
};
this.tâches[idRequête] = tâche;
const fOublierTâche = async () => {
await this.oublierTâche(idRequête);
};
const lorsqueRetour = lorsque(this.événementsInternes, idRequête);
this.envoyerMessageÀIpa(message);
const retour = await lorsqueRetour;
if (retour.type === "erreur") {
this.erreur({
erreur: retour.erreur,
idRequête,
code: retour.codeErreur || ERREUR_EXÉCUTION_IPA,
});
}
if (retour.type === "suivrePrêt") {
const { fonctions } = retour;
if (fonctions && fonctions[0]) {
const retour = {
fOublier: fOublierTâche,
};
for (const f of fonctions) {
retour[f] = async (...args) => {
await this.tâches[idRequête]?.fRetour(f, args);
};
}
return retour;
}
}
return fOublierTâche;
}
async appelerFonctionAction(idRequête, fonction, args) {
const message = {
type: "action",
idRequête,
fonction,
args: args,
};
const lorsqueRetour = lorsque(this.événementsInternes, idRequête);
this.envoyerMessageÀIpa(message);
const retour = await lorsqueRetour;
if (retour.type === "action") {
return retour.résultat;
}
else if (retour.type === "erreur") {
this.erreur({
erreur: retour.erreur,
idRequête,
code: retour.codeErreur || ERREUR_EXÉCUTION_IPA,
});
}
else {
this.erreur({
erreur: `Type de retour ${retour} non reconnu.`,
idRequête,
code: ERREUR_EXÉCUTION_IPA,
});
}
throw new Error("On ne devrait jamais arriver ici.");
}
erreur({ erreur, code, idRequête, }) {
// Si l'IPA n'a pas bien été initialisée, toutes les autres erreurs sont pas très importantes
if (this.dernièreErreur?.code !== ERREUR_INIT_IPA &&
this.dernièreErreur?.code !== ERREUR_INIT_IPA_DÉJÀ_LANCÉ) {
this.dernièreErreur = { erreur, idRequête, code };
}
this.événements.emit("erreur", { idRequête, ...this.dernièreErreur });
throw new Error(JSON.stringify({ idRequête, ...this.dernièreErreur }));
}
async oublierTâche(idRequête) {
const tâche = this.tâches[idRequête];
if (tâche)
await tâche.fRetour("fOublier");
delete this.tâches[idRequête];
}
async recevoirMessageDIpa(message) {
const { type } = message;
switch (type) {
case "suivre": {
const { idRequête, données } = message;
if (!this.tâches[idRequête])
return;
const { fSuivre } = this.tâches[idRequête];
await fSuivre(données);
break;
}
case "action":
case "suivrePrêt":
case "erreur": {
if (message.type === "erreur" && !message.idRequête) {
this.erreur({
erreur: message.erreur,
code: message.erreur || ERREUR_EXÉCUTION_IPA,
});
break;
}
this.événementsInternes.emit(message.idRequête, message);
break;
}
case "confirmation":
// Important - on utiliser l'id du message de retour
this.événementsInternes.emit(message.idRetour, message);
break;
default: {
this.erreur({
code: ERREUR_MESSAGE_INCONNU,
erreur: `Type inconnu ${type} du message ${message}.`,
idRequête: message.idRequête,
});
}
}
}
// Fonctions publiques
suivreErreurs({ f, }) {
this.événements.on("erreur", f);
f(this.dernièreErreur);
return async () => {
this.événements.off("erreur", f);
};
}
}
class Handler {
listeAtributs;
constructor(listeAtributs) {
this.listeAtributs = listeAtributs || [];
}
get(obj, prop) {
// Inscrire ici les fonctions publiques du mandataire qui ne
// doivent pas être envoyées à Constellation
const directes = ["suivreErreurs"];
if (directes.includes(prop)) {
return obj[prop].bind(obj);
}
else {
const listeAtributs = [...this.listeAtributs, prop];
const h = new Handler(listeAtributs);
return new Proxy(obj, h);
}
}
apply(target, _thisArg, args) {
return target.__call__(this.listeAtributs, args[0]);
}
}
export const générerMandataire = (mandataireClient) => {
const handler = new Handler();
return new Proxy(mandataireClient, handler);
};
//# sourceMappingURL=mandataire.js.map