UNPKG

@constl/mandataire

Version:

Un mandataire très léger pour le client Constellation.

258 lines 9.64 kB
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