// script.js (module)
import {
- connectToAlerts,
- onAlert,
- disconnectFromAlerts,
- isConnected
+ connectToAlerts,
+ onAlert,
+ disconnectFromAlerts,
+ isConnected
} from './socketService.js';
-const SERVER_URL = 'http://localhost:3000';
+// URL dynamique — fonctionne en local ET en production
+const SERVER_URL = window.location.origin;
-// Exemple : récupérer un userId (ici exemple statique, adapte-toi)
-const userId = 'user-123';
+// userId courant
+let userId = localStorage.getItem('wallette_userId') || 'user-123';
+
+// Pré-remplir le champ userId
+const userInput = document.getElementById("userIdInput");
+if (userInput) userInput.value = userId;
// Connecter automatiquement à l'ouverture
connectToAlerts(userId);
-
-//function api
-
-// Charger historique alertes
-async function loadAlertHistory() {
- try {
- const res = await fetch(`${SERVER_URL}/api/alerts/history?userId=${userId}`);
- const data = await res.json();
-
- data.forEach(alert => {
- // on réutilise EXACTEMENT la même logique que socket
- handleAlert(alert);
- });
-
- } catch (err) {
- console.error("Erreur historique alertes :", err);
- }
+// ─────────────────────────────────────────────────────────────
+// UTILITAIRE : appel API générique
+// ─────────────────────────────────────────────────────────────
+async function apiFetch(path) {
+ const res = await fetch(SERVER_URL + path);
+ const json = await res.json();
+ if (!json.ok) throw new Error(json.error?.message || 'Erreur API');
+ return json.data;
}
-// Charger prix actuel
+// ─────────────────────────────────────────────────────────────
+// PRIX ACTUEL
+// ─────────────────────────────────────────────────────────────
async function loadCurrentPrice(pair = "BTC/EUR") {
- try {
- const res = await fetch(`${SERVER_URL}/api/prices/current?pair=${pair}`);
- const data = await res.json();
-
- const priceEl = document.getElementById("price");
- if (priceEl && data.price !== undefined) {
- const currency = pair.includes("USD") ? "USD" : "EUR";
- priceEl.textContent = Number(data.price).toLocaleString('fr-FR', {
- style: 'currency',
- currency: currency
- });
+ try {
+ const data = await apiFetch(`/api/price/current?pair=${encodeURIComponent(pair)}`);
+ const priceEl = document.getElementById("price");
+ if (!priceEl) return;
+ const currency = pair.includes("USD") ? "USD" : "EUR";
+ priceEl.textContent = Number(data.current_price).toLocaleString('fr-FR', {
+ style: 'currency', currency
+ });
+ } catch (err) {
+ console.error("Erreur prix actuel :", err);
+ const priceEl = document.getElementById("price");
+ if (priceEl) priceEl.textContent = "Indisponible";
}
-
- } catch (err) {
- console.error("Erreur prix actuel :", err);
- }
}
-// Charger wallet utilisateur
-async function loadWallet() {
- try {
- const res = await fetch(`${SERVER_URL}/api/wallet/${userId}`);
- const data = await res.json();
+// ─────────────────────────────────────────────────────────────
+// WALLETS
+// ─────────────────────────────────────────────────────────────
+async function loadWallets() {
+ const walletList = document.getElementById("walletList");
+ if (!walletList) return;
+
+ try {
+ const result = await apiFetch(`/api/wallets?userId=${encodeURIComponent(userId)}`);
+ const wallets = result.wallets || result || [];
+
+ walletList.innerHTML = '';
+
+ if (!wallets || wallets.length === 0) {
+ walletList.innerHTML = `<li class="list-group-item bg-transparent text-white">Aucun wallet chargé</li>`;
+ return;
+ }
+
+ const first = wallets[0];
+ const balanceEl = document.getElementById("balance");
+ if (balanceEl) balanceEl.textContent = `${first.balance ?? '0'} ${first.asset_symbol || 'BTC'}`;
+
+ wallets.forEach(wallet => {
+ const li = document.createElement('li');
+ li.className = 'list-group-item bg-transparent text-white';
+ li.style.cursor = 'pointer';
+ li.textContent = `${wallet.asset_symbol || '?'} — ${wallet.balance ?? '—'}`;
+ li.onclick = () => loadWalletEvents(wallet.wallet_id);
+ walletList.appendChild(li);
+ });
+
+ if (first.wallet_id) loadWalletEvents(first.wallet_id);
+
+ } catch (err) {
+ console.error("Erreur wallet :", err);
+ walletList.innerHTML = `<li class="list-group-item bg-transparent text-danger">Erreur : ${err.message}</li>`;
+ }
+}
- const balanceEl = document.getElementById("balance");
- if (balanceEl && data.balance !== undefined) {
- balanceEl.textContent = data.balance + " BTC";
+// ─────────────────────────────────────────────────────────────
+// HISTORIQUE TRANSACTIONS
+// ─────────────────────────────────────────────────────────────
+async function loadWalletEvents(walletId) {
+ const eventsList = document.getElementById("walletEvents");
+ if (!eventsList) return;
+
+ try {
+ const events = await apiFetch(`/api/wallets/${walletId}/events`);
+
+ eventsList.innerHTML = '';
+
+ if (!events || events.length === 0) {
+ eventsList.innerHTML = `<li class="list-group-item bg-transparent text-white">Aucune transaction</li>`;
+ return;
+ }
+
+ events.forEach(ev => {
+ const li = document.createElement('li');
+ li.className = 'list-group-item bg-transparent text-white d-flex justify-content-between';
+ const date = new Date(ev.timestamp_ms || ev.created_at_ms || Date.now()).toLocaleString('fr-FR');
+ const typeClass = ev.event_type === 'CREDIT' ? 'text-success' : 'text-danger';
+ li.innerHTML = `
+ <span><strong class="${typeClass}">${ev.event_type || '?'}</strong>
+ <small class="text-muted ms-2">${date}</small></span>
+ <span>${ev.amount ?? '—'} ${ev.asset_symbol || ''}</span>
+ `;
+ eventsList.appendChild(li);
+ });
+
+ } catch (err) {
+ console.error("Erreur historique transactions :", err);
}
+}
- } catch (err) {
- console.error("Erreur wallet :", err);
- }
+// ─────────────────────────────────────────────────────────────
+// SIGNAL ACTUEL
+// ─────────────────────────────────────────────────────────────
+async function loadCurrentSignal() {
+ try {
+ const signal = await apiFetch(`/api/signal/current?userId=${encodeURIComponent(userId)}&pair=BTC/EUR`);
+
+ const box = document.getElementById('signalBox');
+ const actionEl = document.getElementById('signalAction');
+ const critEl = document.getElementById('signalCriticality');
+ const confEl = document.getElementById('signalConfidence');
+ const reasonEl = document.getElementById('signalReason');
+
+ if (!box) return;
+
+ const action = signal.action || 'HOLD';
+ box.className = 'signal-box ' + action.toLowerCase();
+ if (actionEl) actionEl.textContent = action;
+ if (critEl) critEl.textContent = signal.criticality || signal.alertLevel || 'INFO';
+ if (confEl) confEl.textContent = typeof signal.confidence === 'number'
+ ? Math.round(signal.confidence * 100) + '%' : '—';
+ if (reasonEl) reasonEl.textContent = signal.reason || '—';
+
+ } catch (err) {
+ const reasonEl = document.getElementById('signalReason');
+ if (reasonEl) reasonEl.textContent = 'Signal non disponible';
+ }
}
-// Quand une alerte arrive, l'ajouter dans la liste #alertList
+// ─────────────────────────────────────────────────────────────
+// GESTION ALERTES SOCKET.IO
+// ─────────────────────────────────────────────────────────────
function handleIncomingAlert(alert) {
+ console.log('Nouvelle alerte reçue :', alert);
- console.log('Nouvelle alerte reçue dans main.js :', alert);
-
- const list = document.getElementById('alertList');
- if (!list) return;
-
- const li = document.createElement('li');
+ const list = document.getElementById('alertList');
+ if (!list) return;
- li.className = 'list-group-item bg-transparent text-white';
+ const placeholder = list.querySelector('li');
+ if (placeholder && placeholder.textContent.includes('Aucune')) placeholder.remove();
- // Si l'alerte est un objet, essaye d'afficher message et type
- if (typeof alert === 'object') {
- li.textContent = (alert.message ? alert.message : JSON.stringify(alert));
- } else {
- li.textContent = String(alert);
- }
-
- // 1) Supprimer "Aucune alerte" si présent
- const placeholder = document.getElementById('noAlerts');
- if (placeholder) placeholder.remove();
-
- // 2) Appliquer couleur selon BUY / SELL / HOLD
- if (typeof alert === 'object' && alert.action) {
- const action = alert.action.toUpperCase();
- if (action === 'BUY') li.classList.add('buy');
- else if (action === 'SELL') li.classList.add('sell');
- else li.classList.add('hold');
- }
-
- // 3) Mettre à jour le bloc signal (si présent)
- if (typeof alert === 'object') {
- const box = document.getElementById('signalBox');
+ const box = document.getElementById('signalBox');
const actionEl = document.getElementById('signalAction');
- const critEl = document.getElementById('signalCriticality');
- const confEl = document.getElementById('signalConfidence');
+ const critEl = document.getElementById('signalCriticality');
+ const confEl = document.getElementById('signalConfidence');
const reasonEl = document.getElementById('signalReason');
- if (box && actionEl && critEl && confEl && reasonEl) {
- const action = alert.action || 'HOLD';
- box.className = 'signal-box ' + action.toLowerCase();
- actionEl.textContent = action;
- critEl.textContent = alert.alertLevel || 'INFO';
- confEl.textContent =
- typeof alert.confidence === 'number'
- ? Math.round(alert.confidence * 100) + '%'
- : '—';
- reasonEl.textContent = alert.reason || alert.message || '—';
+ if (typeof alert === 'object') {
+ const action = (alert.action || 'HOLD').toUpperCase();
+ if (box) box.className = 'signal-box ' + action.toLowerCase();
+ if (actionEl) actionEl.textContent = action;
+ if (critEl) critEl.textContent = alert.alertLevel || 'INFO';
+ if (confEl) confEl.textContent = typeof alert.confidence === 'number'
+ ? Math.round(alert.confidence * 100) + '%' : '—';
+ if (reasonEl) reasonEl.textContent = alert.reason || alert.message || '—';
}
- }
-
- // 4) Notification popup simple (si container existe)
- const popupContainer = document.getElementById('popupContainer');
- if (popupContainer) {
- const pop = document.createElement('div');
- pop.className = 'notification-popup';
- pop.textContent = li.textContent;
- popupContainer.appendChild(pop);
-
- setTimeout(() => pop.remove(), 6000);
- }
-
- if (typeof alert === 'object') {
-
- // Date & heure
- const d = new Date(alert.timestamp || Date.now());
- const dateStr = d.toLocaleString('fr-FR', {
- day: '2-digit',
- month: '2-digit',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit'
- });
-
- // Level
- const level = alert.alertLevel || alert.level || 'INFO';
- // Action
- const action = alert.action || 'HOLD';
+ const li = document.createElement('li');
+ li.className = 'list-group-item bg-transparent text-white';
+
+ if (typeof alert === 'object') {
+ const d = new Date(alert.timestamp || Date.now());
+ const dateStr = d.toLocaleString('fr-FR', {
+ day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit'
+ });
+
+ const level = (alert.alertLevel || alert.level || 'INFO').toUpperCase();
+ const action = (alert.action || 'HOLD').toUpperCase();
+
+ let badgeClass = 'bg-info text-dark';
+ if (level === 'CRITICAL') badgeClass = 'bg-danger';
+ else if (level === 'WARNING') badgeClass = 'bg-warning text-dark';
+
+ let actionClass = 'text-warning';
+ if (action === 'BUY') actionClass = 'text-success';
+ if (action === 'SELL' || action === 'STOP_LOSS') actionClass = 'text-danger';
+
+ const currency = (alert.pair || '').includes('USD') ? 'USD' : 'EUR';
+ const priceStr = alert.price != null
+ ? Number(alert.price).toLocaleString('fr-FR', { style:'currency', currency }) : '';
+
+ const pair = alert.pair || '';
+ const conf = typeof alert.confidence === 'number'
+ ? ` • confiance ${Math.round(alert.confidence * 100)}%` : '';
+
+ li.innerHTML = `
+ <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
+ <small class="text-muted">${dateStr}</small>
+ <span class="badge ${badgeClass}">${level}</span>
+ <strong class="${actionClass}">${action}</strong>
+ ${priceStr ? `<span style="font-weight:600;">${priceStr}</span>` : ''}
+ </div>
+ <div style="margin-top:4px; font-size:0.85rem; color:#cbd5e1;">
+ ${pair}${conf}
+ </div>
+ `;
+ } else {
+ li.textContent = String(alert);
+ }
- // Prix (si présent)
- let priceStr = '';
- if (alert.price !== undefined) {
- const currency = (alert.pair && alert.pair.includes('USD')) ? 'USD' : 'EUR';
- priceStr = Number(alert.price).toLocaleString('fr-FR', {
- style: 'currency',
- currency: currency
- });
+ const popupContainer = document.getElementById('popupContainer');
+ if (popupContainer) {
+ const pop = document.createElement('div');
+ pop.className = 'notification-popup';
+ pop.textContent = `${alert.action || ''} ${alert.pair || ''} — ${alert.reason || ''}`;
+ popupContainer.appendChild(pop);
+ setTimeout(() => pop.remove(), 6000);
}
- // Pair + confiance
- const pair = alert.pair || '';
- const conf = typeof alert.confidence === 'number'
- ? ` • confiance ${Math.round(alert.confidence * 100)}%`
- : '';
-
- // Badge couleur level
- let badgeClass = 'bg-primary';
- const lvl = level.toUpperCase();
-
- if (lvl.includes('DANGER') || lvl.includes('CRITICAL')) badgeClass = 'bg-danger';
- else if (lvl.includes('WARNING')) badgeClass = 'bg-warning text-dark';
- else if (lvl.includes('INFO')) badgeClass = 'bg-info text-dark';
-
- // Appliquer affichage formaté (remplace le texte brut)
- li.innerHTML = `
- <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
- <small class="text-muted">${dateStr}</small>
- <span class="badge ${badgeClass}">${lvl}</span>
- <strong>${action}</strong>
- ${priceStr ? `<span style="font-weight:600;">${priceStr}</span>` : ''}
- </div>
- <div style="margin-top:4px; font-size:0.85rem; color:#cbd5e1;">
- ${pair}${conf}
- </div>
- `;
- }
-
-
- // Préfixer pour voir les nouvelles alertes en haut
- list.prepend(li);
+ list.prepend(li);
}
-// Socket temps réel
-onAlert(function(alert) {
- handleIncomingAlert(alert);
-});
+onAlert(handleIncomingAlert);
+
+// ─────────────────────────────────────────────────────────────
+// BOUTONS
+// ─────────────────────────────────────────────────────────────
+document.getElementById("saveUserBtn")?.addEventListener("click", () => {
+ const input = document.getElementById("userIdInput");
+ if (input?.value.trim()) {
+ userId = input.value.trim();
+ localStorage.setItem('wallette_userId', userId);
+ alert("User ID sauvegardé : " + userId);
+ loadWallets();
+ loadCurrentSignal();
+ }
+ });
-loadAlertHistory();
-loadCurrentPrice();
-loadWallet();
+ document.getElementById("connectBtn")?.addEventListener("click", () => {
+ connectToAlerts(userId);
+ const st = document.getElementById("connStatus");
+ if (st) { st.className = "badge bg-success"; st.textContent = "Statut : on"; }
+ });
-// Déconnexion propre à la fermeture de la page
-window.addEventListener('beforeunload', () => {
- if (isConnected()) {
+ document.getElementById("disconnectBtn")?.addEventListener("click", () => {
disconnectFromAlerts();
- }
+ const st = document.getElementById("connStatus");
+ if (st) { st.className = "badge bg-secondary"; st.textContent = "Statut : off"; }
+ });
+
+ // ─────────────────────────────────────────────────────────────
+ // CHARGEMENT INITIAL
+ // ─────────────────────────────────────────────────────────────
+ loadCurrentPrice();
+ loadWallets();
+ loadCurrentSignal();
+ setInterval(loadCurrentPrice, 30000);
+ setInterval(loadCurrentSignal, 15000);
+
+ window.addEventListener('beforeunload', () => {
+ if (isConnected()) disconnectFromAlerts();
});
-// Exposer pour debug dans console
-window._alertsService = { connectToAlerts, disconnectFromAlerts, isConnected };
\ No newline at end of file
+window._alertsService = { connectToAlerts, disconnectFromAlerts, isConnected };