CSM-APP est une application Electron locale de gestion de fiches de personnages JDR.
Elle fonctionne hors ligne, stocke les données côté utilisateur (%APPDATA%/CSM) et propose des modules MJ/Joueur, un magasin CSV, une carte tactique, des exports/sauvegardes et des options de synchronisation (WebSocket, MySQL optionnel, API distante en préparation).
Version actuelle : 6.1.4
%APPDATA%/CSM/Campagnes/<campagne>/<personnage>/.Flèches, Carreaux, Balles), ajout/suppression de lignes et affichage/consommation directe dans le Dashboard via Tirer / Récupérer.Relation en lecture seule par défaut puis édition à la demande via Éditer.Déplacement difficile avec coût personnalisé, Entrée, Sortie, Objet, Piège/rune) dans la vue MJ, la vue joueur et la fenêtre dédiée, avec un sélecteur commun Entrée / Sortie côté MJ, des réglages d’outil regroupés sous les boutons et une visibilité configurable pour les pièges/runes (Visible par tous ou MJ uniquement). Lorsqu'un pion entre sur un piège/rune, son déplacement s'arrête sur cette case et le repère devient visible par tous s'il était réservé au MJ.mode, fiche, campagne, sélection MJ) et rechargement correct si la fenêtre est déjà ouverte..csv), normalisation de titre et limite de taille des payloads CSV.%APPDATA% durcie : extraction ZIP validée dans un dossier de staging puis remplacement contrôlé des racines métier autorisées.window.open et blocage des navigations hors file:// sur les fenêtres applicatives.file: autorisées par la CSP et résolution plus fiable après migration/suppression d’éléments.%APPDATA%; le backup distant SFTP manuel a été retiré au profit de la synchronisation BDD et des backups locaux.update-runner.js) pour appliquer l'archive hors process principal.%APPDATA%/CSM.database.enabled: false par défaut + toggle BDD dans ⚙️ Paramètres) avec compte SQL runtime séparé du compte de migration et rôles Joueur/MJ stockés en base par campagne.csm-api aux fiches JSON, assets image, carte tactique, ennemis et restauration de backups, avec switch Mode API dans ⚙️ Paramètres pour basculer depuis la BDD legacy.Etat_personnage (1 actif, 0 inactif) et une suppression locale peut désactiver la fiche en base sans supprimer définitivement ses données.expectedRevision). Si la fiche distante a avancé pendant qu'une modification locale reste non envoyée, l'application détecte les champs divergents et demande de choisir entre distant plus récent, local non envoyé ou fusion avant d'écrire.MJ-{utilisateur CSM}.%APPDATA%/CSM/config.json, affichage temporaire via bouton œil et reset depuis app/config/config.json en lecture seule.CSM-APP/
├── app/
│ ├── assets/
│ │ ├── picture/ # .png, .jpg
│ │ ├── audio/ # .mp3
│ │ ├── icons/ # .ico, .icns
│ │ └── bin/ # .exe (ex: RustDesk)
│ ├── config/ # config.example.json (+ config.json de base local, non versionné)
│ │ ├── keys/ # update-public.pem
│ ├── controllers/ # scripts renderer et utilitaires front
│ ├── data/ # données embarquées (craft, herbier, magasin, rule-doc)
│ ├── scripts/ # scripts de fonctionnalités
│ ├── start-loader/ # splash screen
│ ├── styles/ # CSS
│ ├── tools/ # services/main helpers organisés par domaine
│ │ ├── core/ # menu, logs, mail, configuration utilisateur chiffrée
│ │ ├── sync/ # sync MySQL/realtime + combat
│ │ ├── storage/ # backup et persistance fichiers métier
│ │ ├── update/ # auto-update (manifest, runner, launcher)
│ │ └── utils/ # normalisation, migration, helpers de chemin/nommage
│ └── views/ # modules UI (dashboard, inventaire, personnage, etc.)
│ ├── shared/ # composants partagés renderer (notifications, sync, settings, pdf...)
│ └── modules/ # vues métier (MJ, carte tactique, enemy-manager...)
├── main.js # process principal Electron (orchestrateur)
├── preload.js # API IPC exposée au renderer
├── csm-api/ # backend API HTTP pour la migration hors SQL direct client
├── realtime-sync-server/
│ ├── .env.example # modèle de variables Docker/WebSocket
│ ├── server.js # serveur WebSocket + endpoints admin
│ └── docker-compose.yml
├── README.md
├── SECURITY.md
└── AGENTS.md
Depuis 6.0.1, les gros blocs ont été extraits en modules dédiés :
app/tools/utils/mainNormalizers.js, app/tools/utils/mainNormalizersAdapter.js, app/tools/sync/combatSyncService.js, app/tools/storage/zipImportService.js, app/tools/core/configService.js, app/tools/core/appMenu.js.app/views/shared/realtime-notifications.js, sync-controls.js, renderer-ipc-bindings.js, settings-view.js, pdf-browser.js, combat-map-shared.js.app/views/modules/carte-tactique/state-engine.js, app/views/modules/carte-tactique/persistence.js, app/views/modules/mj/combat-map-engine.js.app/views/modules/carte-tactique/carte-tactique.js et app/views/modules/mj/mj.js (scripts chargés via app/index.html).Cette organisation conserve les IPC existants et vise surtout la maintenabilité (moins de logique inline dans main.js et app/renderer.js).
Installer Node.js (18+ recommandé).
Cloner le dépôt.
Installer les dépendances :
npm install
Créer la configuration de base locale si nécessaire :
cp app/config/config.example.json app/config/config.json
Lancer l’application :
npm start
Générer un build :
npm run build
Vérifier le code JavaScript avec ESLint :
npm run lint
Les fiches personnages sont stockées dans %APPDATA%/CSM avec une arborescence campagne/personnage regroupée dans Campagnes :
%APPDATA%/CSM/
└── Campagnes/
└── <campagne>/
└── <personnage>/
├── fiche.json
├── images/
├── magasin/
└── utilitaires/
Au démarrage, l’application détecte et migre automatiquement les anciens dossiers mes_fiches, images, mj_fiches, campagnes/<campagne>/mes_fiches et les anciennes campagnes stockées directement sous %APPDATA%/CSM/<campagne>/ vers cette structure, puis supprime les anciennes racines devenues inutiles. La mise à niveau des champs de fiche utilise un cache d'état (migration-fiches-state.json) pour éviter de relire toutes les fiches si les fichiers et le modèle embarqué n'ont pas changé, avec écriture atomique des fiches et du cache de migration. Les tableaux de fiche indexés par nom sont fusionnés avec le modèle sans supprimer les entrées personnalisées de l'utilisateur. Les fichiers legacy non migrables sont conservés dans %APPDATA%/CSM/migration-orphelins pour éviter toute perte de données, et l'outil de nettoyage AppData relance ce déplacement avant de supprimer les anciennes racines restantes. Les backups automatiques sont purgés par date de nommage en plus de la date disque, afin d'éviter la conservation d'anciens snapshots dont le LastWriteTime a été modifié. Les imports/exports de fiche ZIP utilisent aussi cette arborescence, tout en gardant la compatibilité avec les anciennes archives. L'export d'une fiche archive le dossier personnage complet (fiche.json, images, magasin, utilitaires) et ajoute les fichiers magasin effectifs hérités de la campagne ou du magasin global si la fiche en dépend. L'import passe par un dossier de staging; si un dossier personnage existe déjà, l'ancien contenu est déplacé dans migration-orphelins/import-fiche-* avant restauration.
app/config/config.json + %APPDATA%/CSM/config.json)Le fichier d’exemple est app/config/config.example.json. En développement, app/config/config.json sert uniquement de configuration de base locale lue par l’application. Les paramètres de connexion modifiés depuis l’application sont stockés séparément dans %APPDATA%/CSM/config.json, prennent le dessus sur la base, chiffrent les valeurs sensibles et sont écrits via une sauvegarde atomique. Aucune action de l’interface ne modifie app/config/config.json.
Pour les contributeurs et agents IA, app/config/config.json est un fichier local ignoré par Git et peut contenir les paramètres privés du poste. Il ne doit jamais être modifié, réinitialisé, assaini, formaté ni remplacé pendant une intervention. Les changements de modèle se font dans app/config/config.example.json; les changements utilisateur passent par %APPDATA%/CSM/config.json via configService.js.
Sections disponibles :
sftp : hôte/port/identifiants réservés à la mise à jour automatique.mail : paramètres SMTP pour l’envoi d’e-mails depuis l’application.updateSigning : validation de la signature de l’archive ZIP de mise à jour.
enabled: true active la vérification.publicKeyPath ou publicKeyPem contient la clé publique de vérification.database : synchronisation MySQL/MariaDB (désactivée par défaut), comptes SQL et noms de tables.
appUser / appPassword sont les identifiants SQL du compte runtime utilisé par l'application au quotidien. Ce compte doit lire/écrire les données CSM, mais ne doit pas créer ni migrer le schéma.migrationUser / migrationPassword sont les identifiants SQL réservés à l'initialisation ou migration manuelle du schéma. Les anciens champs adminUser / adminPassword restent lus par compatibilité comme compte de migration, jamais comme fallback runtime.csm_users, pas des comptes MySQL/MariaDB.charactersTable, usersTable, campaignsTable, membershipsTable définissent les tables de synchronisation.backupTable stocke l'historique JSON des modifications/suppressions.auditTable stocke les logs applicatifs de la synchro.assetTable stocke les images de fiche sous forme de blobs (avatar, relation, gamecard) avec métadonnées et hash SHA-256.assetMaxFileSizeMb limite la taille d'une image synchronisée en BDD (10 Mo par défaut; prévoir un max_allowed_packet MariaDB supérieur, par exemple 64M).projectionTablePrefix définit le préfixe des tables relationnelles annexes (csm_character_spells, csm_character_inventory, etc.).pollIntervalMs règle la récupération quasi temps réel des fiches distantes.remoteApi : configuration cliente préparatoire pour l'API CSM distante (désactivée par défaut).
url pointe vers le backend csm-api ou son reverse proxy.apiToken est récupéré par Tester l'API depuis le login/mot de passe CSM, puis stocké via configService.js.timeoutMs règle le timeout HTTP client (30 s par défaut) ; les timeouts réseau et réponses API temporaires sont rejoués brièvement.realtimeSync : URL WebSocket, canal, clé API et délai de reconnexion.
Les toggles Synchronisation WebSocket et Synchronisation BDD sont mémorisés comme préférences applicatives et restaurés au démarrage. La configuration BDD/WebSocket reste séparée : activer un toggle ne crée pas automatiquement une configuration distante si les champs requis sont absents.
La vue ⚙️ Paramètres permet aussi de modifier les champs BDD (host, port, database, appUser, appPassword, migrationUser, migrationPassword), API CSM (remoteApi.url, remoteApi.apiToken) et WebSocket (url, channel, apiKey). Ces champs sont verrouillés par défaut. La configuration BDD et la configuration API sont affichées côte à côte; le switch Mode API active remoteApi.enabled, grise le panneau BDD et verrouille les actions BDD legacy, tandis que le mode BDD grise le panneau API. Les champs de contexte (host, port, database, url, channel) restent affichés; les secrets identiques à app/config/config.json sont remplacés par un placeholder et ne sont pas transmis en clair à l’interface. Les valeurs modifiées dans %APPDATA%/CSM/config.json restent éditables avec le bouton œil lorsque leur panneau est actif. L’enregistrement écrit uniquement dans %APPDATA%/CSM/config.json.
Les valeurs de connexion utilisateur stockées dans %APPDATA%/CSM/config.json sont chiffrées avec safeStorage (coffre système Electron/OS) lorsque l’application écrit le fichier. Le fichier peut donc contenir des objets techniques __csmEncrypted; c’est attendu. Au démarrage, les anciennes valeurs AppData encore en clair sont migrées vers ce format chiffré. Si le coffre système n’est pas disponible pour une écriture sensible, l’application refuse l’enregistrement plutôt que basculer en clair. Les anciens mots de passe BDD utilisateur passwordEncoding: "plain" sont ignorés et purgés au prochain enregistrement. Le fichier app/config/config.json reste une configuration de base locale lue telle quelle et n’est pas modifié.
Le bouton Reset paramètres recharge les valeurs BDD/WebSocket reconnues depuis app/config/config.json, puis met à jour %APPDATA%/CSM/config.json. Le fichier source app/config/config.json est uniquement lu et n’est jamais modifié par l’application.
⚠️ Ne jamais modifier, committer ni partager
app/config/config.json; ne jamais committer de secrets dansconfig.example.json; ne jamais partager%APPDATA%/CSM/config.jsonmême si les valeurs utilisateur y sont chiffrées.
La synchronisation MySQL/MariaDB complète le stockage local et la synchro WebSocket sans les remplacer.
database.enabled: true dans la configuration locale effective (app/config/config.json ou override %APPDATA%/CSM/config.json), puis toggle Synchronisation BDD dans ⚙️ Paramètres.database.ssl.csm_users.csm_users.password_hash). Si un utilisateur existe sans hash, son premier test de connexion initialise ce hash.csm_users.role; chaque campagne peut surcharger ce rôle via csm_user_campaigns.role.Etat_personnage = 1.Etat_personnage = 0 au lieu de supprimer la ligne SQL.Etat_personnage = 0 restent en base avec leur JSON et leurs backups pour permettre une restauration future.dernier_acces / source_updated_at) : la fiche la plus récente gagne.avatar, relations[].image et gamecards[].image sont synchronisées dans assetTable en LONGBLOB.sha256 avant transfert; si le fichier local manque et que l'asset distant existe, l'application le restaure dans %APPDATA%/CSM/Campagnes/<campagne>/<personnage>/images/.sheet_hash, timestamps, assets sans BLOB) puis récupère les JSON/BLOB uniquement quand un pull est nécessaire.connectionLimit, 4 par défaut) et fermées explicitement à l'arrêt.version + lastUpdate) pour ignorer la synchronisation complète quand rien n'a changé./v1/campaigns/mj. Si la source distante n'est pas prête, elle garde les campagnes locales en fallback.pollIntervalMs) utilise le même snapshot conditionnel : il vérifie les versions avant de lancer une synchronisation complète, ce qui évite de relire et reprojeter toutes les fiches quand aucun changement n'est détecté.SELECT * sur l'état combat.deleted_at) pour éviter une perte immédiate.sheet_json) pour garantir une restauration sans perte.csm_combat_maps conserve l'état autoritaire de la carte tactique par campagne (map_json, enemy_manager_json, state_version) lorsque la BDD est activée, avec révisions séparées map_state_version / enemy_manager_state_version et horodatages de source dédiés._sync.character.expectedRevision). Une écriture BDD est refusée si la révision actuelle dépasse cette valeur, sauf résolution explicite du conflit par l'utilisateur.pollIntervalMs).csm_character_backups pour aider à restaurer en cas d'erreur.Le dossier csm-api/ contient le premier backend HTTP de migration hors accès SQL direct client. Il se lance côté serveur avec :
npm run api:start
Variables d'environnement principales :
CSM_API_HOST / CSM_API_PORT : adresse d'écoute (127.0.0.1:8788 par défaut).CSM_API_TOKEN_SECRET : secret fort pour signer les tokens Bearer. Sans valeur explicite, un secret éphémère est seulement accepté en écoute locale.CSM_API_ALLOWED_ORIGINS : origines CORS autorisées, séparées par virgule.CSM_API_DB_HOST, CSM_API_DB_PORT, CSM_API_DB_NAME, CSM_API_DB_USER, CSM_API_DB_PASSWORD : overrides serveur pour garder les secrets SQL hors client distribué.Endpoints initiaux :
GET /health : état du service.GET /ready : vérification MariaDB/MySQL par SELECT 1, sans détail sensible.POST /v1/auth/login : authentification CSM depuis csm_users.GET /v1/me : identité portée par le token Bearer.GET /v1/campaigns/mj : campagnes où l'utilisateur courant est MJ.GET /v1/characters : liste des fiches accessibles à l'utilisateur courant.GET /v1/characters?campaign=<campagne>&name=<nom> : charge une fiche JSON.POST /v1/characters : sauvegarde une fiche JSON avec expectedRevision.DELETE /v1/characters?campaign=<campagne>&name=<nom> : désactive logiquement une fiche.Les endpoints fiche appliquent les rôles MJ/Joueur côté serveur, filtrent Etat_personnage = 1, écrivent les backups/audits et renvoient un conflit 409 si la révision distante dépasse expectedRevision. Les endpoints assets (/v1/character-assets) transfèrent les images référencées en base64 JSON, valident le type image et restent bornés par assetMaxFileSizeMb et CSM_API_BODY_LIMIT_BYTES.
Les flux tactiques passent par /v1/combat-state quand remoteApi est actif : la carte tactique et le gestionnaire d'ennemis conservent leurs versions séparées pour détecter les conflits de domaine. Les restaurations utilisent /v1/character-backups et /v1/character-backups/restore, puis réécrivent localement la fiche et ses assets récupérés via l'API.
Côté application, app/tools/sync/remoteSyncService.js fournit la couche cliente HTTP. Le switch Mode API de la vue Paramètres écrit remoteApi.enabled. Quand le mode API est actif, la zone Connexion / Compte reste disponible : Tester l'API appelle /v1/auth/login avec le compte CSM, récupère le token Bearer puis l'enregistre dans remoteApi.apiToken. Quand remoteApi.enabled, remoteApi.url et remoteApi.apiToken sont configurés, les chargements/sauvegardes/suppressions de fiches autoritaires, la liste des campagnes MJ, les assets, la carte tactique, les ennemis et les restaurations de backups passent par l'API. Si le mode API est désactivé, le flux SQL direct reste le mode legacy. La vue Paramètres présente les panneaux BDD et API côte à côte et verrouille visuellement le panneau qui ne correspond pas au mode actif.
Depuis csm-api 0.1.2, la sauvegarde API alimente aussi les colonnes métier de csm_character_sheets (pv, pm, pc, classe, niveau, etc.) et toutes les tables de projection csm_character_* couvertes par le mode BDD legacy, en plus du JSON complet sheet_json. Un serveur API déjà déployé doit donc être reconstruit/redémarré pour bénéficier des projections SQL. Après redéploiement, Sync maintenant en mode API rejoue aussi la sauvegarde des fiches dont le JSON est déjà identique afin de remplir les projections existantes.
Depuis csm-api 0.1.3, les écritures API sensibles rejouent automatiquement les transactions interrompues par un deadlock ou un timeout de verrou MySQL (ER_LOCK_DEADLOCK / ER_LOCK_WAIT_TIMEOUT). Ce retry court couvre les sauvegardes et suppressions de fiches, les assets, l'état de combat et les restaurations de backup afin de réduire les erreurs Erreur serveur API observées sous charge de synchronisation. Côté application, les appels HTTP API utilisent un timeout de 30 secondes par défaut, rejouent brièvement les erreurs réseau temporaires et évitent de lancer le polling pendant une sauvegarde de fiche. Le mode BDD legacy applique aussi un retry borné côté application sur les sauvegardes/suppressions/restaurations critiques.
Les publications WebSocket de stats personnage sont déclenchées après sauvegarde locale et ciblées sur la campagne de la fiche. Le WebSocket sert au relayage immédiat vers les autres clients; l'API ou la BDD restent la source autoritaire pour les chargements explicites, les conflits, les snapshots reconstruits et les commits distants en arrière-plan.
Le bouton Sync maintenant reste disponible en mode API si l'URL et le token sont présents. Il lance une synchronisation complète bidirectionnelle : les fiches locales plus récentes sont poussées vers csm-api, les fiches distantes plus récentes ou absentes localement sont récupérées, et les assets référencés sont synchronisés dans les deux sens. Pendant cette synchronisation complète, l'overlay applicatif bloque l'interface et affiche l'avancement reçu du processus principal. Si le token API enregistré expire, le process principal tente de le renouveler avec le compte CSM enregistré, puis rejoue l'appel API. Les sauvegardes unitaires de fiche passent par save-fiche puis sauvegarderFicheEtDiffuser, qui appelle la source autoritaire active sauf demande explicite skipDatabaseSync. En mode API prêt, la synchronisation de démarrage et le polling utilisent aussi csm-api; le polling ne rejoue pas les fiches strictement identiques afin d'éviter de reprojeter inutilement les données SQL côté serveur.
Le smoke test API utilise CSM_API_URL ou remoteApi.url :
npm run test:api
csm-api peut tourner en conteneur sur un serveur Docker qui accède à MariaDB (NAS Synology, hôte Docker interne, VPS privé, etc.). La BDD doit rester privée; exposer uniquement l'API via un reverse proxy HTTPS.
Fichiers fournis :
csm-api/Dockerfile : image Node.js production pour l'API.csm-api/docker-compose.yml : compose d'exemple autonome.csm-api/.env.example : modèle de variables d'environnement, à copier en .env hors Git.Build local depuis la racine du dépôt :
npm run api:docker:build
Build autonome depuis csm-api/ :
docker build -t csm-api:0.1.4 .
Déploiement autonome depuis le dossier csm-api/ copié sur le serveur Docker :
cp .env.example .env
docker compose up -d --build
Réglages attendus sur le serveur Docker :
CSM_API_HOST=0.0.0.0 dans le conteneur.CSM_API_TOKEN_SECRET long, aléatoire et non versionné.CSM_API_DB_ENABLED=true quand l'API doit joindre MariaDB.CSM_API_DB_HOST=192.168.1.152 si MariaDB reste sur le NAS Synology du LAN, avec CSM_API_DB_PORT=3306.proxy, vers le service csm-api et le port interne 8788.Schéma créé ou migré uniquement avec le compte migrationUser / migrationPassword :
csm_users : comptes utilisateurs CSM, hash de mot de passe et rôle par défaut.csm_campaigns : campagnes.csm_user_campaigns : appartenance utilisateur/campagne et rôle effectif par campagne.csm_character_sheets : fiche JSON complète, identité, statistiques principales, lore, état métier, Etat_personnage, propriétaire et timestamp de source.csm_character_* : tables relationnelles annexes synchronisées à chaque sauvegarde à partir du JSON complet.csm_combat_maps : état tactique persistant par campagne (carte, pions, murs, cases spéciales, gestion ennemis) avec révision globale de compatibilité et révisions autoritaires séparées pour carte et ennemis.csm_character_backups : snapshots JSON avant modification/suppression (restauration manuelle).csm_database_logs : journal applicatif des opérations BDD (audit).Pour initialiser un compte CSM, créez une ligne csm_users avec login et role (joueur ou mj). Si password_hash est vide, le premier Tester la connexion avec ce login initialise le hash du mot de passe saisi; les tests suivants réutilisent le mot de passe CSM sauvegardé. Les rôles par campagne se règlent ensuite dans csm_user_campaigns.
Créer un compte Joueur :
INSERT INTO csm_users (login, role, password_hash)
VALUES ('nom_joueur', 'joueur', NULL)
ON DUPLICATE KEY UPDATE
role = VALUES(role);
Créer un compte MJ :
INSERT INTO csm_users (login, role, password_hash)
VALUES ('nom_mj', 'mj', NULL)
ON DUPLICATE KEY UPDATE
role = VALUES(role);
Définir ou surcharger le rôle d'un utilisateur pour une campagne :
INSERT INTO csm_campaigns (name)
VALUES ('Nom de la campagne')
ON DUPLICATE KEY UPDATE name = VALUES(name);
INSERT INTO csm_user_campaigns (user_id, campaign_id, role)
SELECT u.id, c.id, 'mj'
FROM csm_users u
JOIN csm_campaigns c ON c.name = 'Nom de la campagne'
WHERE u.login = 'nom_mj'
ON DUPLICATE KEY UPDATE role = VALUES(role);
Remplacer 'mj' par 'joueur' pour limiter l'utilisateur au rôle Joueur sur cette campagne.
Le service realtime-sync-server permet de synchroniser des événements (PV/PM/PC, état, carte tactique, notifications MJ).
Démarrage rapide :
cd realtime-sync-server
cp .env.example .env
docker compose up -d --build
Points importants :
realtime-sync-server/.env.example, à copier en .env hors Git avant déploiement Docker; définir au minimum une API_KEY forte avant toute exposition réseau.proxy et publie le WebSocket via Nginx Proxy Manager vers le service csm-realtime-sync et le port interne 8787.revision, authoritativeRevision, stateVersion / stateUpdatedAt) par domaine (character, combatMap, enemyManager); les versions obsolètes sont ignorées côté application et côté serveur realtime, sauf patch typé déjà validé par la séquence serveur./data/state-revisions.json avec le compose Docker fourni (REVISION_STORE_PATH peut remplacer ce chemin).1.1.6, un patch carte ou gestion ennemis dont la révision locale est en retard peut être rebasé sur la prochaine révision serveur, tandis qu'un snapshot complet obsolète reste refusé.map.token.upsert dédoublonnent les pions joueurs par nom de personnage lorsque deux postes n'ont pas le même identifiant technique pour le pion.combat-map-updated sont envoyés immédiatement après numérotation, sans coalescence serveur, pour conserver l'ordre de séquence avec les stats personnage.sync-snapshot aux clients autorisés plutôt que de rejouer ce patch à froid.server-ack séquencé, puisque le serveur ne lui rediffuse pas son propre événement carte/ennemis/stats.snapshot-request; l'application reconstruit alors un sync-snapshot complet depuis la source autoritaire active (API/BDD pour carte, ennemis et fiches, sinon JSON local). Lorsqu'une demande sert à reconstruire la base d'un patch WebSocket, l'état JSON local courant est préféré pour ne pas annuler un déplacement non encore commité côté API/BDD. Les snapshots reconstruits dont les révisions sont plus anciennes que le registre durable du canal sont refusés.combat-map-move-preview, probes, présence) ne consomment pas la séquence durable du canal afin qu'une preview perdue ne déclenche pas de resynchronisation.revisionScope: "ephemeral") : elle n'est jamais persistée dans l'état de carte et seule la validation finale du déplacement est sauvegardée/synchronisée./admin/* (auth selon variables d’environnement).CSM_REALTIME_SERVER_VERSION, fallback realtime-sync-server/package.json) et la dernière version CSM connue (CSM_LATEST_VERSION, fallback REQUIRED_APP_VERSION, puis manifeste local).API_KEY, les endpoints admin de lecture seule /admin/clients et /admin/stats restent interrogeables par l’application avec la clé configurée pour alimenter le diagnostic temps réel et le résumé MJ.eventType non autorisés et valide les payloads connus avant diffusion (character-stats-updated, combat-map-updated, enemy-manager-updated, combat-map-move-preview, mj-notification, probes MJ).API_KEY, le serveur realtime écoute désormais en 127.0.0.1 par défaut. Une exposition réseau sans clé API nécessite un opt-in explicite (BIND_HOST + ALLOW_INSECURE_NO_API_KEY=true), ce qui n’est pas recommandé.API_KEY comme changez-moi sont ignorées par le serveur realtime et ne comptent pas comme une protection active.WS_HEARTBEAT_*) et une protection backpressure (MAX_SOCKET_BUFFERED_BYTES) ; les événements durables combat-map-updated ne sont pas coalescés après numérotation.sequenceNumber), des ACK client, un mécanisme de replay/résynchronisation (resync-request) et des snapshots de secours côté serveur. Si un trou de séquence est détecté, le client demande le replay avant d'appliquer l'événement suivant; un sync-snapshot fait reprendre la séquence sur la valeur serveur la plus récente. Un snapshot complet ne remet la séquence à zéro que si le serveur demande explicitement resetSequence.operationMode: "patch", operations[]) quand une base locale stable existe; les imports, resets, changements de dimensions ou diffs trop larges restent envoyés en snapshot complet.serverEpoch et un serverSessionId. Si le serveur redémarre, change de session ou annonce une latestSequence inférieure à la dernière séquence locale, le client remet sa séquence locale à zéro, applique un snapshot complet, puis seulement rejoue l'outbox durable.ACK et resync-request restent limités au canal WebSocket associé à la session, ce qui évite qu'une campagne puisse rejouer les événements d'une autre.%APPDATA%/CSM/realtime-outbox.json tant que le serveur n'a pas confirmé leur acceptation (server-ack). Après reconnexion ou ACK perdu, l'application rejoue les entrées en attente avec le même eventId, puis les purge après confirmation.ENFORCE_APP_VERSION=true + REQUIRED_APP_VERSION=x.y.z, les clients hors version sont rejetés (update-required) pour empêcher les sessions multi-version.CSM_REALTIME_SERVER_VERSION; définir CSM_LATEST_VERSION dans l'environnement Docker pour afficher la dernière version CSM disponible dans le dashboard admin.1.1.7, l'image Docker utilise Node 24, l'installation dépend du lockfile via npm ci, et le compose est préparé pour une exposition par Nginx Proxy Manager sur le réseau externe proxy.realtime-sync-server/** doit être versionnée et documentée dans realtime-sync-server/CHANGELOG.md, avec version cohérente entre package.json, package-lock.json, Dockerfile et docker-compose.yml.npm start : lancement de l’application Electron.npm run build : packaging via electron-builder.npm run lint : analyse statique JavaScript via ESLint.npm run lint:fix : applique les corrections automatiques sûres proposées par ESLint.npm run test:db : vérifie la connexion MySQL/MariaDB avec sortie masquée.npm run test:db:benchmark : mesure la latence BDD sur une connexion réutilisée (SELECT 1) avec min, médiane, moyenne, p95 et max.safeStorage, puis vérifient que le port configuré répond bien comme un serveur MySQL/MariaDB avant la connexion SQL. Ils avertissent aussi, sans host complet, si la cible paraît distante, sans TLS ou sur le port SQL standard. Le port BDD peut être modifié depuis la vue Paramètres.npm run test:ws / npm run test:services : vérifie les services distants configurés sans afficher les secrets.npm run tasks:archive : archive les tâches Obsidian statut: done vers CSM-Documentation/99 - Archives/Task - done/ et complète les liens ## Tâches liées des changelogs Obsidian..vscode/settings.json pour activer source.fixAll.eslint à la sauvegarde.dbaeumer.vscode-eslint (voir .vscode/extensions.json).eslint.config.js) sépare les contextes main/preload Node/Electron des scripts renderer/browser et ignore les fichiers packagés ou minifiés.version.json sur le dépôt SFTP de mise à jour.updateSigning.enabled=true, la signature de l’archive ZIP est vérifiée avec la clé publique configurée (publicKeyPath/publicKeyPem) après téléchargement.
version.json.signature (base64 inline), ou fichier détaché distant (version.json.signatureFile ou fallback automatique <archive>.sig).update.zip.part) puis renommée en update.zip, avec remontée de progression temps réel côté UI.size) et, si présent, le hash sha256 avant de préparer l'installation.csm-updater.exe si présent, sinon pyw.exe + csm-updater-launcher.py) copié à la racine portable, puis se ferme pour lui laisser la main.update-state.json) + journal (update-progress.log), relance l'application puis se ferme.app/tools/update/updateRunner.js) reste utilisé comme fallback (avec revérification SHA-256 si fournie dans le manifeste).csm-updater.exe (optionnel, recommandé en portable Windows)Depuis la racine du projet, sur Windows :
py -3 -m pip install --user pyinstaller
py -3 -m PyInstaller --noconfirm --onefile --noconsole --name csm-updater app/tools/update/externalUpdaterLauncher.py
Copy-Item .\dist\csm-updater.exe "CHEMIN_VERS_VOTRE_DOSSIER_PORTABLE\csm-updater.exe" -Force
Le binaire csm-updater.exe sera prioritaire au lancement de l'updater externe.
version.json signéDepuis la racine du projet, la commande suivante orchestre toute la chaîne de mise en production :
npm run update:manifest
Pipeline exécuté automatiquement :
package.json,build/win-unpacked dans dist/CSM_APP.V.{version}.zip,app/config/keys/update-private.pem,version.json (ou un chemin personnalisé via --out).Le script calcule size, sha256, publishedAt et renseigne la signature base64 inline dans le champ signature du version.json.
Des options restent possibles en override (--source, --zip, --version, --privateKey, --out, --notes, --publishedAt) pour des besoins spécifiques CI/CD.
npm run build avant PR.app/assets/CHANGELOG.md) lors de changements majeurs ou flux critiques.jdr@hrefhug.fr.Projet sous licence MIT (voir LICENSE).