Skip to main content

triggerMessage BootNotification

/**

  • Script POC pour envoyer un triggerMessage BootNotification à toutes les bornes
  • UTILISATION:
    1. Ouvrir la console du navigateur (F12) sur votre application Angular
    1. Copier-coller ce script dans la console
    1. Appeler: triggerBootNotificationToAllStations()
  • OU avec URL personnalisée: triggerBootNotificationToAllStations("https://portail.we-go.pro/api")
  • POUR ARRÊTER LE SCRIPT:
    • Appeler: stopBootNotificationScript()
    • OU rafraîchir la page (F5)
  • IMPORTANT: Assurez-vous d'être connecté et que le token d'authentification est présent */

// Variable globale pour contrôler l'arrêt du script let stopBootNotificationScriptFlag = false;

// Fonction pour arrêter le script function stopBootNotificationScript() { stopBootNotificationScriptFlag = true; console.log("🛑 Arrêt du script demandé. Le script s'arrêtera après la requête en cours..."); }

async function triggerBootNotificationToAllStations(customApiUrl = null) { // Réinitialiser le flag d'arrêt stopBootNotificationScriptFlag = false; console.log("🚀 Démarrage de l'envoi de BootNotification à toutes les bornes..."); console.log("💡 Pour arrêter le script, appelez: stopBootNotificationScript()");

try { // Récupérer le token d'authentification depuis localStorage const token = localStorage.getItem("token");

if (!token) {
  throw new Error("Token d'authentification non trouvé dans localStorage (clé: 'token'). Assurez-vous d'être connecté.");
}

// Récupérer le groupe sélectionné
const selectedRoleStr = localStorage.getItem("selectedRole");
if (!selectedRoleStr) {
  throw new Error("selectedRole non trouvé dans localStorage. Assurez-vous d'avoir sélectionné un groupe.");
}

let selectedRole;
try {
  selectedRole = JSON.parse(selectedRoleStr);
} catch (e) {
  throw new Error("Erreur lors du parsing de selectedRole: " + e.message);
}

if (!selectedRole?.groupId) {
  throw new Error("groupId non trouvé dans selectedRole. Structure: " + JSON.stringify(selectedRole));
}

// Détecter l'URL de l'API
let API_URL;
const currentOrigin = window.location.origin;

if (customApiUrl) {
  // URL personnalisée fournie
  API_URL = customApiUrl;
  console.log(`🔗 URL de l'API (personnalisée): ${API_URL}`);
} else if (currentOrigin.includes("localhost") || currentOrigin.includes("127.0.0.1")) {
  // Mode développement
  API_URL = currentOrigin.replace(":4200", ":3000").replace(":4201", ":3000") || "http://localhost:3000";
  console.log(`🔗 URL de l'API (développement): ${API_URL}`);
} else {
  // Mode production - utiliser le même domaine avec /api
  API_URL = currentOrigin + "/api";
  console.log(`🔗 URL de l'API (production): ${API_URL}`);
}
console.log(`👤 Groupe ID: ${selectedRole.groupId}`);
console.log(`🔑 Token présent: ${token ? "Oui (longueur: " + token.length + ")" : "Non"}`);

const groupId = selectedRole.groupId;
const pageSize = 100;
let page = 1;
let allStations = [];
let hasMorePages = true;

// Récupérer toutes les bornes
console.log("📡 Récupération de la liste des bornes...");
while (hasMorePages && !stopBootNotificationScriptFlag) {
  if (stopBootNotificationScriptFlag) {
    console.log("🛑 Arrêt demandé pendant la récupération des bornes");
    break;
  }

  const offset = (page - 1) * pageSize;
  const url = `${API_URL}/charging-stations/group/${groupId}/list?offset=${offset}&limit=${pageSize}`;

  console.log(`📡 Requête: ${url}`);

  // Ajouter un timeout pour la récupération des bornes aussi
  const fetchController = new AbortController();
  const fetchTimeoutId = setTimeout(() => fetchController.abort(), 10000); // 10 secondes pour la liste (peut être plus long)

  let response;
  try {
    response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      signal: fetchController.signal,
    });
    clearTimeout(fetchTimeoutId);
  } catch (fetchError) {
    clearTimeout(fetchTimeoutId);
    if (fetchError.name === "AbortError") {
      throw new Error("Timeout lors de la récupération des bornes (plus de 10 secondes)");
    }
    throw new Error(`Erreur réseau lors de la récupération: ${fetchError.message}`);
  }

  // Vérifier le Content-Type avant de parser
  const contentType = response.headers.get("content-type");
  if (!contentType || !contentType.includes("application/json")) {
    const responseText = await response.text();
    console.error("❌ Réponse non-JSON reçue:");
    console.error(`   Content-Type: ${contentType || "non défini"}`);
    console.error(`   Status: ${response.status} ${response.statusText}`);
    console.error(`   Début de la réponse: ${responseText.substring(0, 200)}...`);

    if (responseText.includes("<!doctype") || responseText.includes("<html")) {
      throw new Error(
        `L'API a retourné du HTML au lieu de JSON. Cela indique probablement:\n` +
        `- L'URL de l'API est incorrecte (actuelle: ${API_URL})\n` +
        `- Une redirection vers une page d'erreur\n` +
        `- Un problème d'authentification\n\n` +
        `Vérifiez que l'URL de l'API est correcte. Si vous êtes en production, l'API devrait être sur: ${currentOrigin}/api`
      );
    }
    throw new Error(`Réponse non-JSON: ${response.status} ${response.statusText}`);
  }

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}\n${errorText}`);
  }

  let data;
  try {
    data = await response.json();
  } catch (parseError) {
    const responseText = await response.text();
    console.error("❌ Erreur lors du parsing JSON:");
    console.error(`   Réponse reçue: ${responseText.substring(0, 500)}...`);
    throw new Error(`Erreur parsing JSON: ${parseError.message}`);
  }

  const totalRecords = parseInt(response.headers.get("X-Total-Count") || "0");

  allStations = allStations.concat(data.records || data);

  const totalPages = Math.ceil(totalRecords / pageSize);
  hasMorePages = page < totalPages;
  page++;

  console.log(`✅ Page ${page - 1}/${totalPages} récupérée (${allStations.length} bornes trouvées)`);
}

console.log(`\n📊 Total: ${allStations.length} bornes trouvées\n`);

// Filtrer les bornes avec un ID valide
const stationsWithId = allStations.filter((station) => station.id !== undefined && station.id !== null);
console.log(`📋 ${stationsWithId.length} bornes avec ID valide\n`);

if (stationsWithId.length === 0) {
  console.warn("⚠️ Aucune borne avec ID valide trouvée!");
  return [];
}

// Demander confirmation
const confirmMessage = `Voulez-vous envoyer BootNotification à ${stationsWithId.length} bornes?\n\nLe script continuera même si certaines bornes échouent.`;
if (!confirm(confirmMessage)) {
  console.log("❌ Opération annulée par l'utilisateur");
  return [];
}

// Envoyer le triggerMessage BootNotification à chaque borne
const results = [];
let successCount = 0;
let errorCount = 0;
let timeoutCount = 0;
let networkErrorCount = 0;

console.log(`\n🚀 Démarrage de l'envoi à ${stationsWithId.length} bornes...`);
console.log(`💡 Le script continuera même si certaines bornes échouent ou ne répondent pas.\n`);

for (let i = 0; i < stationsWithId.length; i++) {
  // Vérifier si l'arrêt a été demandé
  if (stopBootNotificationScriptFlag) {
    console.log(`\n🛑 Arrêt du script demandé. Arrêt après ${i}/${stationsWithId.length} bornes traitées.`);
    break;
  }

  const station = stationsWithId[i];
  const stationId = station.id;
  const stationName = station.name || station.chargeboxIdentity || `Borne ${stationId}`;

  try {
    console.log(`[${i + 1}/${stationsWithId.length}] Envoi BootNotification à ${stationName} (ID: ${stationId})...`);

    const triggerUrl = `${API_URL}/ocpp/trigger-message/${stationId}`;

    // Créer un AbortController pour gérer les timeouts
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 1000); // Timeout de 5 secondes

    let triggerResponse;
    try {
      triggerResponse = await fetch(triggerUrl, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          command: "BootNotification",
          connector: 0,
        }),
        signal: controller.signal, // Ajouter le signal pour le timeout
      });
      clearTimeout(timeoutId); // Annuler le timeout si la requête réussit
    } catch (fetchError) {
      clearTimeout(timeoutId);
      if (fetchError.name === "AbortError") {
        timeoutCount++;
        throw new Error("Timeout: La requête a pris plus de 1 secondes");
      }
      networkErrorCount++;
      throw new Error(`Erreur réseau: ${fetchError.message}`);
    }

    if (!triggerResponse.ok) {
      const errorText = await triggerResponse.text();
      throw new Error(`HTTP ${triggerResponse.status}: ${errorText}`);
    }

    // Vérifier le Content-Type
    const triggerContentType = triggerResponse.headers.get("content-type");
    if (!triggerContentType || !triggerContentType.includes("application/json")) {
      const errorText = await triggerResponse.text();
      throw new Error(`Réponse non-JSON (${triggerContentType}): ${errorText.substring(0, 200)}`);
    }

    let triggerResult;
    try {
      triggerResult = await triggerResponse.json();
    } catch (parseError) {
      const errorText = await triggerResponse.text();
      throw new Error(`Erreur parsing JSON: ${parseError.message}. Réponse: ${errorText.substring(0, 200)}`);
    }

    // Afficher la réponse complète pour les 3 premières requêtes (débogage)
    if (i < 3) {
      console.log(`  🔍 Réponse complète (débogage):`, JSON.stringify(triggerResult, null, 2));
    }

    // Extraire le statut selon différentes structures possibles
    let status = null;
    let errorMessage = null;

    if (Array.isArray(triggerResult)) {
      // Si c'est un tableau, prendre le premier élément
      const firstResult = triggerResult[0];
      if (firstResult) {
        status = firstResult.result?.status || firstResult.status;
        errorMessage = firstResult.error || firstResult.result?.error;
      }
    } else if (triggerResult && typeof triggerResult === "object") {
      // Si c'est un objet
      status = triggerResult.result?.status || triggerResult.status;
      errorMessage = triggerResult.error || triggerResult.result?.error;

      // Vérifier aussi si la réponse contient directement un message
      if (!status && !errorMessage) {
        // Peut-être que la réponse est directement le statut
        status = triggerResult.message || triggerResult.data?.status;
      }
    }

    // Si on n'a toujours rien trouvé, afficher la structure complète
    if (!status && !errorMessage) {
      console.warn(`  ⚠️ Structure de réponse inattendue pour la borne ${stationId}:`, triggerResult);
      errorMessage = `Structure de réponse inattendue: ${JSON.stringify(triggerResult).substring(0, 100)}`;
    }

    // Déterminer le succès
    const isSuccess = status === "Accepted" || status === "accepted";

    if (isSuccess) {
      successCount++;
      results.push({ id: stationId, name: stationName, success: true, status: status });
      console.log(`  ✅ Succès: ${status}`);
    } else {
      errorCount++;
      const finalError = errorMessage || status || "Statut inconnu";
      results.push({ id: stationId, name: stationName, success: false, error: String(finalError), status: status });
      console.log(`  ❌ Échec: ${finalError}${status ? ` (status: ${status})` : ""}`);
    }
  } catch (error) {
    errorCount++;
    const errorMessage = error instanceof Error ? error.message : String(error);
    results.push({ id: stationId, name: stationName, success: false, error: errorMessage });
    console.log(`  ❌ Erreur: ${errorMessage}`);
    // Le script continue avec la borne suivante même en cas d'erreur
    console.log(`  ⏭️ Passage à la borne suivante...`);
  }

  // Petit délai pour éviter de surcharger le serveur
  if (i < stationsWithId.length - 1) {
    await new Promise((resolve) => setTimeout(resolve, 100));
  }
}

// Résumé
console.log("\n" + "=".repeat(60));
console.log("📊 RÉSUMÉ");
console.log("=".repeat(60));
if (stopBootNotificationScriptFlag) {
  console.log("⚠️ Script arrêté par l'utilisateur");
}
console.log(`✅ Succès: ${successCount}`);
console.log(`❌ Échecs totaux: ${errorCount}`);
if (timeoutCount > 0) {
  console.log(`⏱️ Timeouts: ${timeoutCount}`);
}
if (networkErrorCount > 0) {
  console.log(`🌐 Erreurs réseau: ${networkErrorCount}`);
}
console.log(`📋 Total traité: ${results.length}/${stationsWithId.length}`);
if (stopBootNotificationScriptFlag && results.length < stationsWithId.length) {
  console.log(`⏸️ Arrêté avant la fin (${stationsWithId.length - results.length} bornes non traitées)`);
} else if (results.length === stationsWithId.length) {
  console.log(`✨ Toutes les bornes ont été traitées!`);
}
console.log("=".repeat(60));

// Afficher les échecs
const failures = results.filter((r) => !r.success);
if (failures.length > 0) {
  console.log("\n❌ Bornes en échec:");
  failures.forEach((f) => {
    console.log(`  - ${f.name} (ID: ${f.id}): ${f.error}`);
  });
}

return results;

} catch (error) { console.error("❌ Erreur fatale:", error); throw error; } }

// Exporter pour utilisation dans la console if (typeof window !== "undefined") { window.triggerBootNotificationToAllStations = triggerBootNotificationToAllStations; window.stopBootNotificationScript = stopBootNotificationScript; console.log("✅ Script chargé!"); console.log("📝 Commandes disponibles:"); console.log(" - triggerBootNotificationToAllStations() : Démarrer"); console.log(" - stopBootNotificationScript() : Arrêter le script en cours"); }