/// <summary> /// Logs a session and returns session information on success. /// </summary> public static void LogSession(MonoBehaviour mb, ClientArgs args, Guid userId, string detail, string revisionId, Action<Guid, string> onSuccess, Action<string> onFailure) { var data = new Dictionary<string, object>(); data.Add("user_id", userId); data.Add("release_id", args.ReleaseId); // XXX (kasiu): Need to check if this is the right DateTime string to send. I THINK THIS IS WRONG BUT I DON'T GIVE A FOOBAR RIGHT NOW. FIXME WHEN THE SERVER SCREAMS. data.Add("client_time", DateTime.Now.ToString()); data.Add("detail", detail); data.Add("library_revid", revisionId); // Processing to get session_id Action<string> callback = s => { var s2 = s.Replace("{", "").Replace("}", "").Replace("\"", "").Trim(); var split = s2.Split(','); if (split.Length != 2) { onFailure(string.Format("LogSession received ill-formatted JSON: {0}", s)); } var sessionId = split[0].Split(':')[1].Trim(); var sessionKey = split[1].Split(':')[1].Trim(); onSuccess(new Guid(sessionId), sessionKey); }; var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/session"), args); SendNonSessionRequest(mb, newArgs, data, callback, onFailure); }
/// <summary> /// Queries for an experimental condition (integer), which is assigned on success. /// </summary> public static void QueryExperimentalCondition(MonoBehaviour mb, ClientArgs args, Guid userId, Guid experimentId, Action<int> onSuccess, Action<string> onFailure) { var data = new Dictionary<string, object>(); data.Add("user_id", userId); data.Add("experiment_id", experimentId); Action<string> callback = s => { // HACK (kasiu): Again, more delightful condition retrieval without the pain of real JSON parsing. var split = s.Split(':'); if (split.Length != 2) { onFailure(string.Format("QueryExperimentalCondition received ill-formatted JSON: {0}", s)); } var conditionStr = split[1].Replace("}", "").Trim(); onSuccess(int.Parse(conditionStr)); }; var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/experiment"), args); SendNonSessionRequest(mb, newArgs, data, callback, onFailure); }
/// <summary> /// Tests the telemetry server by writing some hilarious dummy values. /// Left public to invoke externally if you desire. /// </summary> public void TestTelemetryServer() { Debug.Log("Testing the telemetry server"); // Set up some dummy values for testing. var releaseId = Guid.NewGuid(); var releaseKey = Guid.NewGuid().ToString(); var clientArgs = new ClientArgs(serverUri, releaseId, releaseKey); // TESTING QUERY USER ID ----> and some other stuff. var userId = Guid.Empty; var experimentalCondition = -1; Action<string> onSuccess = s => { Debug.Log("OnSuccess: " + s); }; Action<string> onFailure = s => { Debug.LogError("OnFailure: " + s); }; Action<int> setExperimentalConditionOnSuccess = i => { experimentalCondition = i; Debug.Log("Got successful user experiment condition: " + i); }; Action<string> setUserDataOnSuccess = s => { // Probably could do something with data? Nothing gets returned, so we don't care. // TESTING QUERY DATA Debug.Log("Testing that QueryUserData works..."); UnityBackend.QueryUserData(this, clientArgs, userId, onSuccess, onFailure); }; Action<string> setOnLogEventOnSuccess = s => { Debug.Log("Succesfully logged event: " + s); }; Action<Guid, string> setOnLogSessionOnSuccess = (g, s) => { Debug.Log("Got session id: " + g.ToString()); Debug.Log("Got session key: " + s); // TESTING LOG EVENTS (just going to log some root events because f**k tasks for now) var eventDetail = new Dictionary<string, object>(); eventDetail.Add("PANCAKES", "HAMSTERS"); var eventDict = new Dictionary<string, object>(); eventDict.Add("category_id", 42); eventDict.Add("type_id", 666); eventDict.Add("session_sequence_index", 1); eventDict.Add("client_time", DateTime.Now.ToString()); eventDict.Add("detail", MicroJSON.Serialize(eventDetail)); var events = new object[] { eventDict }; UnityBackend.LogEvents(this, serverUri, events, g, s, setOnLogEventOnSuccess, onFailure); }; Action<Guid> setUserIdOnSuccess = g => { userId = g; Debug.Log("Got successful user id: " + g.ToString()); // TESTING QUERY EXPERIMENTAL CONDITION Debug.Log("Testing that QueryExperimentalCondition works..."); UnityBackend.QueryExperimentalCondition(this, clientArgs, userId, new Guid("00000000-0000-0000-0000-000000000000"), setExperimentalConditionOnSuccess, onFailure); // TESTING SAVE/QUERY USER DATA Debug.Log("Testing that SaveUserData works..."); var saveData = new Dictionary<string, object>(); saveData.Add("I'm some", new object[] { "save", "data" }); UnityBackend.SetUserData(this, clientArgs, userId, MicroJSON.Serialize(saveData), setUserDataOnSuccess, onFailure); // TESTING LOG SESSION Debug.Log("Testing that LogSession works..."); var sessionData = new Dictionary<string, object>(); sessionData.Add("i'm", "some_data"); sessionData.Add("with", new object[] { 2, "arrays" }); UnityBackend.LogSession(this, clientArgs, userId, MicroJSON.Serialize(sessionData), "UNHAPPY ID", setOnLogSessionOnSuccess, onFailure); }; Debug.Log("Testing that QueryUserId works..."); UnityBackend.QueryUserId(this, clientArgs, "dedennehblehs", setUserIdOnSuccess, onFailure); }
/// <summary> /// Sends a non-session request. /// </summary> private static void SendNonSessionRequest(MonoBehaviour mb, ClientArgs args, Dictionary<string, object> data, Action<string> onSuccess, Action<string> onFailure) { var values = new Dictionary<string, object>(); values.Add("version", PROTOCOL_VERSION); values.Add("data", MicroJSON.Serialize(data)); values.Add("release", args.ReleaseId); values.Add("checksum", string.Empty); var jsonString = MicroJSON.Serialize(values); mb.StartCoroutine(SendPostRequest(args.BaseUri, jsonString, onSuccess, onFailure)); }
/// <summary> /// Queries the user id. /// </summary> public static void QueryUserId(MonoBehaviour mb, ClientArgs args, string username, Action<Guid> onSuccess, Action<string> onFailure) { var data = new Dictionary<string, object>(); data.Add("username", username); Action<string> callback = s => { // HACK (kasiu): Get the Guid out without real JSON parsing. var split = s.Split(':'); if (split.Length != 2) { onFailure(string.Format("QueryUserId received ill-formatted JSON: {0}", s)); } var s2 = split[1]; var startIndex = s2.IndexOf('"') + 1; // +1 past the first quote var endIndex = s2.IndexOf('"', startIndex); var guid = s2.Substring(startIndex, endIndex - startIndex); onSuccess(new Guid(guid)); }; var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/user"), args); SendNonSessionRequest(mb, newArgs, data, callback, onFailure); }
/// <summary> /// Sets user data. /// </summary> public static void SetUserData(MonoBehaviour mb, ClientArgs args, Guid userId, string savedata, Action<string> onSuccess, Action<string> onFailure) { var data = new Dictionary<string, object>(); data.Add("id", userId); data.Add("savedata", savedata); // We don't need to implement a special callback here, since we don't get anything back. var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/user/set_data"), args); SendNonSessionRequest(mb, newArgs, data, onSuccess, onFailure); }
/// <summary> /// Queries for any saved user data. /// </summary> public static void QueryUserData(MonoBehaviour mb, ClientArgs args, Guid userId, Action<string> onSuccess, Action<string> onFailure) { var data = new Dictionary<string, object>(); data.Add("id", userId); // TODO (kasiu): Process the user data here? var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/user/get_data"), args.ReleaseId, args.ReleaseKey); SendNonSessionRequest(mb, newArgs, data, onSuccess, onFailure); }
/// <summary> /// Unity Start() /// </summary> private void Start() { #if UNITY_EDITOR this.serverUri = new Uri(DevServer); #else this.serverUri = new Uri(ProdServer); #endif // Set up some dummy values for testing. var releaseId = Guid.NewGuid(); var releaseKey = Guid.NewGuid().ToString(); var clientArgs = new ClientArgs(serverUri, releaseId, releaseKey); // TESTING QUERY USER ID ----> and some other stuff. var userId = Guid.Empty; var experimentalCondition = -1; var pClient = this.gameObject.GetComponent<PapikaClient>(); if (pClient == null) { throw new NullReferenceException("Please make sure the PapikaClient component is also attached to the game object with this component."); } pClient.Initialize(clientArgs); Action<string> onSuccess = s => { Debug.Log("OnSuccess: " + s); }; Action<string> onFailure = s => { Debug.LogError("OnFailure: " + s); }; Action<int> setExperimentalConditionOnSuccess = i => { experimentalCondition = i; Debug.Log("Got successful user experiment condition: " + i); }; Action<string> setUserDataOnSuccess = s => { // Probably could do something with data? Nothing gets returned, so we don't care. // TESTING QUERY DATA Debug.Log("Testing that QueryUserData works..."); pClient.QueryUserData(userId, onSuccess, onFailure); }; Action<Guid> setUserIdOnSuccess = g => { userId = g; Debug.Log("Got successful user id: " + g.ToString()); // TESTING QUERY EXPERIMENTAL CONDITION Debug.Log("Testing that QueryExperimentalCondition works..."); pClient.QueryExperimentalCondition(userId, new Guid("00000000-0000-0000-0000-000000000000"), setExperimentalConditionOnSuccess, onFailure); // TESTING SAVE/QUERY USER DATA Debug.Log("Testing that SaveUserData works..."); var saveData = new Dictionary<string, object>(); saveData.Add("I'm some", new object[] { "save", "data (again)" }); pClient.SetUserData(userId, MicroJSON.Serialize(saveData), setUserDataOnSuccess, onFailure); // TESTING LOG SESSION Debug.Log("Testing that LogSession works..."); var sessionData = new Dictionary<string, object>(); sessionData.Add("i'm", "some_data"); sessionData.Add("with", new object[] { 2, "arrays (maybe)" }); pClient.StartSession(userId, MicroJSON.Serialize(sessionData)); // TESTING LOG EVENTS (just going to log some root events) // XXX (kasiu): Does not test task logging yet. Debug.Log("Testing that LogEvent works..."); var eventDetail = new Dictionary<string, object>(); eventDetail.Add("WAFFLES", "HAMSTERS"); pClient.Root.LogEvent(42, 667, MicroJSON.Serialize(eventDetail)); }; pClient.QueryUserId("I love cheesecake", setUserIdOnSuccess, onFailure); }
// PUBLIC CLIENT FUNCTIONS! /// <summary> /// Sets the client-side configuration arguments. /// This should be called in a Unity Start() call, probably. /// </summary> public void Initialize(ClientArgs args) { this.config = args; }
/// <summary> /// Unity Awake() /// </summary> private void Awake() { // this may be called multiple times if scene gets reloaded, don't reinitialize! // Process editor parameters or assume user will call initialize with them later. if (this.config == null && !(string.IsNullOrEmpty(BaseUri) || string.IsNullOrEmpty(ReleaseId) || string.IsNullOrEmpty(ReleaseKey))) { this.config = new ClientArgs(new Uri(BaseUri), new Guid(ReleaseId), ReleaseKey); } if (this.eventsToLog == null) { this.eventsToLog = new List<object>(); this.sessionSequenceCounter = 1; this.sessionId = null; this.sessionKey = null; this.isFlushEventsLocked = false; this.taskIdCounter = 1; this.wasStartSessionCalled = false; this.Root = new TaskLogger(null, logEvent, getTaskId); } }
/// <summary> /// Constructor. /// This one is used internally to copy over non-Uri args and really /// shouldn't be used outside of backend code. /// </summary> public ClientArgs(Uri newUri, ClientArgs oldArgs) { BaseUri = newUri; ReleaseId = oldArgs.ReleaseId; ReleaseKey = oldArgs.ReleaseKey; }