diff --git a/Assets/Scenes/Demo.unity b/Assets/Scenes/Demo.unity index 9ad132c..735ab88 100644 --- a/Assets/Scenes/Demo.unity +++ b/Assets/Scenes/Demo.unity @@ -22176,7 +22176,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!114 &292487262 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/BodyLinkHandler.cs b/Assets/Scripts/BodyLinkHandler.cs index 4291344..e991f59 100644 --- a/Assets/Scripts/BodyLinkHandler.cs +++ b/Assets/Scripts/BodyLinkHandler.cs @@ -47,14 +47,14 @@ public class BodyLinkHandler : MonoBehaviour, IPointerClickHandler, IPointerExit EmailData email = emailPanel.Email; if (email != null) { - SceneOutcomeManager.Instance?.Clicked(email); //UserActionLogger.Instance?.Log($"Clicked link '{linkID}' in email from '{email.senderName}'"); string englishLog = $"Clicked link '{linkID}' in email from '{email.senderName}'"; string arabicLog = $"تم الضغط على الرابط '{linkID}' في البريد من '{email.senderName}'"; - UserActionLogger.Instance?.Log(englishLog, arabicLog); + bool isOptimal = !email.isPhishing; SupabaseEventLogger.Instance?.LogDecisionEvent(isOptimal); + SceneOutcomeManager.Instance?.Clicked(email); } else diff --git a/Assets/Scripts/UserActionLogger.cs b/Assets/Scripts/UserActionLogger.cs index a5b7ccd..bf6a3dd 100644 --- a/Assets/Scripts/UserActionLogger.cs +++ b/Assets/Scripts/UserActionLogger.cs @@ -65,21 +65,42 @@ public class UserActionLogger : MonoBehaviour [ContextMenu("ShowSummary")] public void ShowSummary() { - if (summaryText != null) - { + if (summaryText == null) + return; + + string logs = GetFullLog(); + + // Default to showing original logs + summaryText.text = logs; - bool isArabic = LanguageManager.Instance != null && - LanguageManager.Instance.currentLanguage == "Arabic"; - string logs = GetFullLog(); - if (isArabic) - { - summaryText.text = ArabicFixer.Fix(logs); - summaryText.font = LanguageManager.Instance.fontArabic; - summaryText.ForceMeshUpdate(); - } + // If Arabic language is active, fix and apply Arabic font + if (LanguageManager.Instance != null && + LanguageManager.Instance.currentLanguage == "Arabic") + { + summaryText.text = ArabicFixer.Fix(logs); + summaryText.font = LanguageManager.Instance.fontArabic; } + + summaryText.ForceMeshUpdate(); } + //public void ShowSummary() + //{ + // if (summaryText != null) + // { + + // bool isArabic = LanguageManager.Instance != null && + // LanguageManager.Instance.currentLanguage == "Arabic"; + // string logs = GetFullLog(); + // if (isArabic) + // { + // summaryText.text = ArabicFixer.Fix(logs); + // summaryText.font = LanguageManager.Instance.fontArabic; + // summaryText.ForceMeshUpdate(); + // } + // } + //} + public void ClearLog() { logs.Clear(); diff --git a/Assets/SupabaseEventLogger.cs b/Assets/SupabaseEventLogger.cs index 02b4d97..603d21d 100644 --- a/Assets/SupabaseEventLogger.cs +++ b/Assets/SupabaseEventLogger.cs @@ -1,15 +1,17 @@ using UnityEngine; +using UnityEngine.Networking; using System; +using System.Collections; using System.Collections.Generic; -using System.Threading.Tasks; -using Supabase; // Make sure SupabaseManager initializes this correctly -using Postgrest.Models; -using Postgrest.Attributes; public class SupabaseEventLogger : MonoBehaviour { public static SupabaseEventLogger Instance; + [Header("Supabase")] + public string supabaseUrl = "https://vihjspljbslozbjzxutl.supabase.co"; + public string supabaseAnonKey = "YOUR_ANON_KEY_HERE"; + private DateTime sessionStartTime; private void Awake() @@ -20,161 +22,539 @@ public class SupabaseEventLogger : MonoBehaviour Destroy(gameObject); } - /// - /// Call this at the start of the game session. - /// - public async void StartSession() + [Serializable] + public class GameEventPayload + { + public string event_key; + public string timestamp; + public string user_id; + } + + [Serializable] + public class GameAttemptPayload + { + public string game_id; + public string scenario_id; + public string user_id; + public int attempt_number; + public string start_timestamp; + public string end_timestamp; + public int duration_seconds; + public float final_score_percentage; + public bool pass_fail_status; + public int optimal_decisions_count; + public int suboptimal_decisions_count; + public string key_decisions_log; + } + + [Serializable] + public class Decision + { + public string decisionId; + public string timestamp; + public bool optimal; + } + + [Serializable] + public class DecisionLogWrapper + { + public List decisions; + } + + public void StartSession() { sessionStartTime = DateTime.UtcNow; - var gameEvent = new GameEvent + GameEventPayload payload = new GameEventPayload { - Id = Guid.NewGuid(), // <== Ensure this is explicitly set - EventKey = "game_session_started", - Timestamp = sessionStartTime, - UserId = "user123" + event_key = "game_session_started", + timestamp = sessionStartTime.ToString("o"), + user_id = "user123" }; - await Client.Instance.From().Insert(gameEvent); - Debug.Log("✅ Supabase Event: game_session_started"); + + StartCoroutine(PostToSupabase("game_events", JsonUtility.ToJson(payload))); } - /// - /// Logs optimal/suboptimal decisions at runtime. - /// - public async void LogDecisionEvent(bool isOptimal) + public void LogDecisionEvent(bool isOptimal) { string eventKey = isOptimal ? "game_optimal_decision_made" : "game_suboptimal_decision_made"; - var gameEvent = new GameEvent + GameEventPayload payload = new GameEventPayload { - Id = Guid.NewGuid(), - EventKey = eventKey, - Timestamp = DateTime.UtcNow, - UserId = "user123" + event_key = eventKey, + timestamp = DateTime.UtcNow.ToString("o"), + user_id = "user123" }; - await Client.Instance.From().Insert(gameEvent); - Debug.Log($"✅ Supabase Event: {eventKey}"); + StartCoroutine(PostToSupabase("game_events", JsonUtility.ToJson(payload))); } - /// - /// Completes the session and submits full results to phishing_game_attempts table. - /// - public async void CompleteSessionAndSubmitResult(string userId, bool passed, int optimal, int suboptimal, string scenarioId, List decisionLog = null) + public void CompleteSessionAndSubmitResult(string userId, bool passed, int optimal, int suboptimal, string scenarioId, List decisionLog = null) { var endTime = DateTime.UtcNow; int duration = (int)(endTime - sessionStartTime).TotalSeconds; - // Log completion events - await Client.Instance.From().Insert(new GameEvent + // Submit game_session_completed event + GameEventPayload completedEvent = new GameEventPayload { - Id = Guid.NewGuid(), - EventKey = "game_session_completed", - Timestamp = endTime, - UserId = userId - }); - - await Client.Instance.From().Insert(new GameEvent - {Id = Guid.NewGuid(), - EventKey = "game_score_recorded", - Timestamp = endTime, - UserId = userId - }); - - // Insert session result - var gameAttempt = new GameAttempt + event_key = "game_session_completed", + timestamp = endTime.ToString("o"), + user_id = userId + }; + StartCoroutine(PostToSupabase("game_events", JsonUtility.ToJson(completedEvent))); + + // Submit game_score_recorded event + GameEventPayload scoreEvent = new GameEventPayload { - GameId = "phishing-awareness-1", - ScenarioId = scenarioId, - UserId = userId, - AttemptNumber = 1, - StartTime = sessionStartTime, - EndTime = endTime, - DurationSeconds = duration, - Score = passed ? 100 : 50, - Passed = passed, - Optimal = optimal, - Suboptimal = suboptimal, - KeyDecisionsLogJson = decisionLog != null ? JsonUtility.ToJson(new DecisionLogWrapper { decisions = decisionLog }) : "[]" + event_key = "game_score_recorded", + timestamp = endTime.ToString("o"), + user_id = userId }; + StartCoroutine(PostToSupabase("game_events", JsonUtility.ToJson(scoreEvent))); - await Client.Instance.From().Insert(gameAttempt); - Debug.Log("✅ Supabase Game Result Submitted"); - } + // Submit final game attempt data + GameAttemptPayload attempt = new GameAttemptPayload + { + game_id = "phishing-awareness-1", + scenario_id = scenarioId, + user_id = userId, + attempt_number = 1, + start_timestamp = sessionStartTime.ToString("o"), + end_timestamp = endTime.ToString("o"), + duration_seconds = duration, + final_score_percentage = passed ? 100 : 50, + pass_fail_status = passed, + optimal_decisions_count = optimal, + suboptimal_decisions_count = suboptimal, + key_decisions_log = decisionLog != null ? JsonUtility.ToJson(new DecisionLogWrapper { decisions = decisionLog }) : "[]" + }; - [Serializable] - public class Decision - { - public string decisionId; - public string timestamp; - public bool optimal; + StartCoroutine(PostToSupabase("phishing_game_attempts", JsonUtility.ToJson(attempt))); } - [Serializable] - public class DecisionLogWrapper + private IEnumerator PostToSupabase(string table, string jsonBody) { - public List decisions; - } -} - -// Existing SupabaseEventLogger class here... -// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - -[Table("game_events")] -public class GameEvent : BaseModel -{ - [PrimaryKey("id", false)] - public Guid Id { get; set; } + string url = $"{supabaseUrl}/rest/v1/{table}"; + UnityWebRequest request = new UnityWebRequest(url, "POST"); + byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody); + request.uploadHandler = new UploadHandlerRaw(bodyRaw); + request.downloadHandler = new DownloadHandlerBuffer(); - [Column("event_key")] - public string EventKey { get; set; } + request.SetRequestHeader("Content-Type", "application/json"); + request.SetRequestHeader("apikey", supabaseAnonKey); + request.SetRequestHeader("Authorization", "Bearer " + supabaseAnonKey); + request.SetRequestHeader("Prefer", "return=representation"); - [Column("timestamp")] - public DateTime Timestamp { get; set; } + yield return request.SendWebRequest(); - [Column("user_id")] - public string UserId { get; set; } + if (request.result == UnityWebRequest.Result.Success) + { + Debug.Log($"✅ Supabase POST to {table}: " + request.downloadHandler.text); + } + else + { + Debug.LogError($"❌ Supabase POST Failed ({table}): {request.responseCode}\n{request.error}\n{request.downloadHandler.text}"); + } + } } -[Table("phishing_game_attempts")] -public class GameAttempt : BaseModel -{ - [PrimaryKey("id", false)] - public Guid Id { get; set; } = Guid.NewGuid(); - - [Column("game_id")] - public string GameId { get; set; } - [Column("scenario_id")] - public string ScenarioId { get; set; } - [Column("user_id")] - public string UserId { get; set; } +//using UnityEngine; +//using System; +//using System.Collections; +//using System.Collections.Generic; +//using System.Threading.Tasks; +//using Supabase; +//using Postgrest.Models; +//using Postgrest.Attributes; +//using static SupabaseTestInsert; - [Column("attempt_number")] - public int AttemptNumber { get; set; } +//[Table("game_events")] +//public class GameEvent : BaseModel +//{ +// [PrimaryKey("id", false)] +// public Guid Id { get; set; } - [Column("start_timestamp")] - public DateTime StartTime { get; set; } +// [Column("event_key")] +// public string EventKey { get; set; } - [Column("end_timestamp")] - public DateTime EndTime { get; set; } +// [Column("timestamp")] +// public DateTime Timestamp { get; set; } - [Column("duration_seconds")] - public int DurationSeconds { get; set; } +// [Column("user_id")] +// public string UserId { get; set; } +//} - [Column("final_score_percentage")] - public float Score { get; set; } +//[Table("phishing_game_attempts")] +//public class GameAttempt : BaseModel +//{ +// [PrimaryKey("id", false)] +// public Guid Id { get; set; } = Guid.NewGuid(); - [Column("pass_fail_status")] - public bool Passed { get; set; } +// [Column("game_id")] +// public string GameId { get; set; } - [Column("optimal_decisions_count")] - public int Optimal { get; set; } - - [Column("suboptimal_decisions_count")] - public int Suboptimal { get; set; } - - [Column("key_decisions_log")] - public string KeyDecisionsLogJson { get; set; } -} +// [Column("scenario_id")] +// public string ScenarioId { get; set; } + +// [Column("user_id")] +// public string UserId { get; set; } + +// [Column("attempt_number")] +// public int AttemptNumber { get; set; } + +// [Column("start_timestamp")] +// public DateTime StartTime { get; set; } + +// [Column("end_timestamp")] +// public DateTime EndTime { get; set; } + +// [Column("duration_seconds")] +// public int DurationSeconds { get; set; } + +// [Column("final_score_percentage")] +// public float Score { get; set; } + +// [Column("pass_fail_status")] +// public bool Passed { get; set; } + +// [Column("optimal_decisions_count")] +// public int Optimal { get; set; } + +// [Column("suboptimal_decisions_count")] +// public int Suboptimal { get; set; } + +// [Column("key_decisions_log")] +// public string KeyDecisionsLogJson { get; set; } +//} + +//public class SupabaseEventLogger : MonoBehaviour +//{ +// public static SupabaseEventLogger Instance; + +// private DateTime sessionStartTime; + +// private void Awake() +// { +// if (Instance == null) +// Instance = this; +// else +// Destroy(gameObject); +// } + +// public void StartSession() +// { +// StartCoroutine(StartSessionCoroutine()); +// } + +// private IEnumerator StartSessionCoroutine() +// { +// var task = StartSessionAsync(); +// while (!task.IsCompleted) +// yield return null; + +// if (task.Exception != null) +// Debug.LogError("❌ Supabase Error: " + task.Exception.InnerException?.Message); +// } + +// private async Task StartSessionAsync() +// { +// sessionStartTime = DateTime.UtcNow; + +// var gameEvent = new GameEvent +// { +// Id = Guid.NewGuid(), +// EventKey = "game_session_started", +// Timestamp = sessionStartTime, +// UserId = "user123" +// }; + +// await Client.Instance.From().Insert(gameEvent); +// Debug.Log("✅ Supabase Event: game_session_started"); +// } + +// public void LogDecisionEvent(bool isOptimal) +// { +// StartCoroutine(LogDecisionCoroutine(isOptimal)); +// } + +// private IEnumerator LogDecisionCoroutine(bool isOptimal) +// { +// var task = LogDecisionAsync(isOptimal); +// while (!task.IsCompleted) +// yield return null; + +// if (task.Exception != null) +// Debug.LogError("❌ Supabase Error: " + task.Exception.InnerException?.Message); +// } + +// private async Task LogDecisionAsync(bool isOptimal) +// { +// string eventKey = isOptimal ? "game_optimal_decision_made" : "game_suboptimal_decision_made"; + +// var gameEvent = new GameEvent +// { +// Id = Guid.NewGuid(), +// EventKey = eventKey, +// Timestamp = DateTime.UtcNow, +// UserId = "user123" +// }; + +// await Client.Instance.From().Insert(gameEvent); +// Debug.Log($"✅ Supabase Event: {eventKey}"); +// } + +// public void CompleteSessionAndSubmitResult(string userId, bool passed, int optimal, int suboptimal, string scenarioId, List decisionLog = null) +// { +// StartCoroutine(CompleteSessionCoroutine(userId, passed, optimal, suboptimal, scenarioId, decisionLog)); +// } + +// private IEnumerator CompleteSessionCoroutine(string userId, bool passed, int optimal, int suboptimal, string scenarioId, List decisionLog) +// { +// var task = CompleteSessionAsync(userId, passed, optimal, suboptimal, scenarioId, decisionLog); +// while (!task.IsCompleted) +// yield return null; + +// if (task.Exception != null) +// Debug.LogError("❌ Supabase Error: " + task.Exception.InnerException?.Message); +// } + +// private async Task CompleteSessionAsync(string userId, bool passed, int optimal, int suboptimal, string scenarioId, List decisionLog) +// { +// var endTime = DateTime.UtcNow; +// int duration = (int)(endTime - sessionStartTime).TotalSeconds; + +// await Client.Instance.From().Insert(new GameEvent +// { +// Id = Guid.NewGuid(), +// EventKey = "game_session_completed", +// Timestamp = endTime, +// UserId = userId +// }); + +// await Client.Instance.From().Insert(new GameEvent +// { +// Id = Guid.NewGuid(), +// EventKey = "game_score_recorded", +// Timestamp = endTime, +// UserId = userId +// }); + +// var gameAttempt = new GameAttempt +// { +// Id = Guid.NewGuid(), +// GameId = "phishing-awareness-1", +// ScenarioId = scenarioId, +// UserId = userId, +// AttemptNumber = 1, +// StartTime = sessionStartTime, +// EndTime = endTime, +// DurationSeconds = duration, +// Score = passed ? 100 : 50, +// Passed = passed, +// Optimal = optimal, +// Suboptimal = suboptimal, +// KeyDecisionsLogJson = decisionLog != null ? JsonUtility.ToJson(new DecisionLogWrapper { decisions = decisionLog }) : "[]" +// }; + +// await Client.Instance.From().Insert(gameAttempt); +// Debug.Log("✅ Supabase Game Result Submitted"); +// } + +// [Serializable] +// public class Decision +// { +// public string decisionId; +// public string timestamp; +// public bool optimal; +// } + +// [Serializable] +// public class DecisionLogWrapper +// { +// public List decisions; +// } +//} + + +////using UnityEngine; +////using System; +////using System.Collections.Generic; +////using System.Threading.Tasks; +////using Supabase; // Make sure SupabaseManager initializes this correctly +////using Postgrest.Models; +////using Postgrest.Attributes; + +////public class SupabaseEventLogger : MonoBehaviour +////{ +//// public static SupabaseEventLogger Instance; + +//// private DateTime sessionStartTime; + +//// private void Awake() +//// { +//// if (Instance == null) +//// Instance = this; +//// else +//// Destroy(gameObject); +//// } + +//// /// +//// /// Call this at the start of the game session. +//// /// +//// public async void StartSession() +//// { +//// sessionStartTime = DateTime.UtcNow; + +//// var gameEvent = new GameEvent +//// { +//// Id = Guid.NewGuid(), // <== Ensure this is explicitly set +//// EventKey = "game_session_started", +//// Timestamp = sessionStartTime, +//// UserId = "user123" +//// }; +//// await Client.Instance.From().Insert(gameEvent); +//// Debug.Log("✅ Supabase Event: game_session_started"); +//// } + +//// /// +//// /// Logs optimal/suboptimal decisions at runtime. +//// /// +//// public async void LogDecisionEvent(bool isOptimal) +//// { +//// string eventKey = isOptimal ? "game_optimal_decision_made" : "game_suboptimal_decision_made"; + +//// var gameEvent = new GameEvent +//// { +//// Id = Guid.NewGuid(), +//// EventKey = eventKey, +//// Timestamp = DateTime.UtcNow, +//// UserId = "user123" +//// }; + +//// await Client.Instance.From().Insert(gameEvent); +//// Debug.Log($"✅ Supabase Event: {eventKey}"); +//// } + +//// /// +//// /// Completes the session and submits full results to phishing_game_attempts table. +//// /// +//// public async void CompleteSessionAndSubmitResult(string userId, bool passed, int optimal, int suboptimal, string scenarioId, List decisionLog = null) +//// { +//// var endTime = DateTime.UtcNow; +//// int duration = (int)(endTime - sessionStartTime).TotalSeconds; + +//// // Log completion events +//// await Client.Instance.From().Insert(new GameEvent +//// { +//// Id = Guid.NewGuid(), +//// EventKey = "game_session_completed", +//// Timestamp = endTime, +//// UserId = userId +//// }); + +//// await Client.Instance.From().Insert(new GameEvent +//// {Id = Guid.NewGuid(), +//// EventKey = "game_score_recorded", +//// Timestamp = endTime, +//// UserId = userId +//// }); + +//// // Insert session result +//// var gameAttempt = new GameAttempt +//// { +//// GameId = "phishing-awareness-1", +//// ScenarioId = scenarioId, +//// UserId = userId, +//// AttemptNumber = 1, +//// StartTime = sessionStartTime, +//// EndTime = endTime, +//// DurationSeconds = duration, +//// Score = passed ? 100 : 50, +//// Passed = passed, +//// Optimal = optimal, +//// Suboptimal = suboptimal, +//// KeyDecisionsLogJson = decisionLog != null ? JsonUtility.ToJson(new DecisionLogWrapper { decisions = decisionLog }) : "[]" +//// }; + +//// await Client.Instance.From().Insert(gameAttempt); +//// Debug.Log("✅ Supabase Game Result Submitted"); +//// } + +//// [Serializable] +//// public class Decision +//// { +//// public string decisionId; +//// public string timestamp; +//// public bool optimal; +//// } + +//// [Serializable] +//// public class DecisionLogWrapper +//// { +//// public List decisions; +//// } +////} + +////// Existing SupabaseEventLogger class here... +////// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + +////[Table("game_events")] +////public class GameEvent : BaseModel +////{ +//// [PrimaryKey("id", false)] +//// public Guid Id { get; set; } + +//// [Column("event_key")] +//// public string EventKey { get; set; } + +//// [Column("timestamp")] +//// public DateTime Timestamp { get; set; } + +//// [Column("user_id")] +//// public string UserId { get; set; } +////} + +////[Table("phishing_game_attempts")] +////public class GameAttempt : BaseModel +////{ +//// [PrimaryKey("id", false)] +//// public Guid Id { get; set; } = Guid.NewGuid(); + +//// [Column("game_id")] +//// public string GameId { get; set; } + +//// [Column("scenario_id")] +//// public string ScenarioId { get; set; } + +//// [Column("user_id")] +//// public string UserId { get; set; } + +//// [Column("attempt_number")] +//// public int AttemptNumber { get; set; } + +//// [Column("start_timestamp")] +//// public DateTime StartTime { get; set; } + +//// [Column("end_timestamp")] +//// public DateTime EndTime { get; set; } + +//// [Column("duration_seconds")] +//// public int DurationSeconds { get; set; } + +//// [Column("final_score_percentage")] +//// public float Score { get; set; } + +//// [Column("pass_fail_status")] +//// public bool Passed { get; set; } + +//// [Column("optimal_decisions_count")] +//// public int Optimal { get; set; } + +//// [Column("suboptimal_decisions_count")] +//// public int Suboptimal { get; set; } + +//// [Column("key_decisions_log")] +//// public string KeyDecisionsLogJson { get; set; } +////} diff --git a/Build.zip b/Build.zip deleted file mode 100644 index 5acba10..0000000 Binary files a/Build.zip and /dev/null differ diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 3bbf775..b8de345 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -816,14 +816,14 @@ PlayerSettings: webGLExceptionSupport: 1 webGLNameFilesAsHashes: 0 webGLShowDiagnostics: 0 - webGLDataCaching: 1 + webGLDataCaching: 0 webGLDebugSymbols: 0 webGLEmscriptenArgs: webGLModulesDirectory: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 - webGLCompressionFormat: 1 + webGLCompressionFormat: 2 webGLWasmArithmeticExceptions: 0 webGLLinkerTarget: 1 webGLThreadsSupport: 0