using System; using UnityEngine; namespace MyInfoMate.Vr.Net { /// /// Télémétrie de visite — item E9 du lot XR-4. /// /// Les stats du manager fonctionnent sans une ligne de plus côté serveur : /// VisitEvent porte déjà un AppType, et l'écran Statistiques filtre /// génériquement sur AppType.values — le canal VR y apparaîtra tout seul au /// premier événement reçu. /// /// ⚠️ Deux différences avec le reste de l'API, vérifiées dans StatsController : /// la route est POST /api/stats/event (pas /api/visitevent), et /// appType comme eventType partent en chaîne, parsés par nom /// côté serveur. Un nom inconnu d'eventType est un 400 ; un appType /// inconnu retombe silencieusement sur Mobile — c'est-à-dire qu'une faute de /// frappe ici ferait compter les visites du casque dans le canal mobile. /// /// L'envoi est « tire et oublie » : une borne ne doit jamais attendre une statistique, /// et un événement perdu ne vaut pas un message d'erreur au visiteur. /// public class Telemetry { /// Le nom, pas la valeur : le serveur parse par nom (Enum.TryParse). const string AppTypeVr = "VR"; readonly ApiClient _client; readonly string _instanceId; readonly string _configurationId; readonly string _language; /// /// Une session = une visite. Sur une borne, elle se renouvelle à chaque retour au /// menu ou à la repose du casque (E10), pas au lancement de l'app : sinon une /// journée entière de borne compterait pour un seul visiteur. /// public string SessionId { get; private set; } = NewSessionId(); public Telemetry(ApiClient client, string instanceId, string configurationId, string language) { _client = client; _instanceId = instanceId; _configurationId = configurationId; _language = language; } public void StartNewSession() => SessionId = NewSessionId(); public void SectionView(string sectionId) => Send("SectionView", sectionId); public void SectionLeave(string sectionId, int seconds) => Send("SectionLeave", sectionId, seconds); public void MenuItemTap(string sectionId) => Send("MenuItemTap", sectionId); public void MapPoiTap(string sectionId, string poiTitle) => Send("MapPoiTap", sectionId, metadata: $"{{\"poi\":\"{Escape(poiTitle)}\"}}"); async void Send(string eventType, string sectionId, int? durationSeconds = null, string metadata = null) { var result = await _client.PostAsync("/api/stats/event", new { instanceId = _instanceId, configurationId = _configurationId, sectionId, sessionId = SessionId, eventType, appType = AppTypeVr, language = _language, durationSeconds, metadata, timestamp = DateTime.UtcNow }); // La réponse est un 204 sans corps : la désérialisation ne dit rien d'utile, // seul un échec réseau mérite une trace — et rien ne remonte au visiteur. if (!result.Ok) Debug.Log($"[Stats] {eventType} non envoyé : {result.Error}"); } static string NewSessionId() => Guid.NewGuid().ToString("N"); static string Escape(string value) => string.IsNullOrEmpty(value) ? "" : value.Replace("\\", "\\\\").Replace("\"", "\\\""); } }