/// <summary> /// Defines an asset bundle. If a Leanplum variable with the same name and type exists, /// this will return the existing variable. /// </summary> /// <returns>Leanplum variable.</returns> /// <param name="name">Name of variable.</param> /// <param name="realtimeUpdating">Setting it to <c>false</c> will prevent Leanplum from /// reloading assetbundles as they change in development mode.</param> /// <param name="iosBundleName">Filename of iOS assetbundle.</param> /// <param name="androidBundleName">Filename of Android assetbundle.</param> /// <param name="standaloneBundleName">Filename of Standalone assetbundle.</param> public override Var <AssetBundle> DefineAssetBundle(string name, bool realtimeUpdating = true, string iosBundleName = "", string androidBundleName = "", string standaloneBundleName = "") { string platform = LeanplumNative.CompatibilityLayer.GetPlatformName(); string resourceName = Constants.Values.RESOURCES_VARIABLE + '.' + platform + " Assets." + name; string bundleName = String.Empty; if (platform == "iOS") { bundleName = iosBundleName; } else if (platform == "Android") { bundleName = androidBundleName; } else if (platform == "Standalone") { bundleName = standaloneBundleName; } var variable = DefineHelper <AssetBundle>(resourceName, Constants.Kinds.FILE, null); if (variable != null) { variable.SetFilename(bundleName); variable.fileReady = false; variable.realtimeAssetUpdating = realtimeUpdating; VarCache.RegisterVariable(variable); variable.Update(); } return(variable); }
private void Update() { // Workaround so that CheckVarsUpdate() is invoked on Unity's main thread. // This is called by Unity on every frame. if (VarCache.VarsNeedUpdate && developerModeEnabled && Leanplum.HasStarted) { VarCache.CheckVarsUpdate(); } // Run deferred actions. List <Action> actions = null; lock (delayed) { if (delayed.Count > 0) { actions = new List <Action>(delayed); delayed.Clear(); } } if (actions != null) { foreach (Action action in actions) { action(); } } }
public override void DefineAction(string name, Constants.ActionKind kind, ActionArgs args, IDictionary <string, object> options, ActionContext.ActionResponder responder) { if (string.IsNullOrWhiteSpace(name)) { CompatibilityLayer.LogError($"Empty name parameter."); return; } if (args == null) { CompatibilityLayer.LogError($"Args cannot be null."); return; } var ad = new ActionDefinition(); ad.Name = name; ad.Kind = kind; ad.Args = args; ad.Options = options; if (responder != null) { ad.Responder += responder; } VarCache.RegisterActionDefinition(ad); }
private void OnSocketMessage(object obj, MessageEventArgs e) { if (e.Message.MessageType == SocketIOMessageTypes.Event && !String.IsNullOrEmpty(e.Message.MessageText)) { IDictionary <string, object> messageReceived = Json.Deserialize(e.Message.MessageText) as IDictionary <string, object>; string eventName = messageReceived.ContainsKey("name") ? messageReceived["name"] as string: ""; if (eventName == "updateVars") { onUpdateVars(); } else if (eventName == "getVariables") { bool sentValues = VarCache.SendVariablesIfChanged(); Dictionary <string, bool> response = new Dictionary <string, bool>(); response.Add("updated", sentValues); socketIOClient.Emit("getContentResponse", response); } else if (eventName == "getActions") { // Unsupported in LeanplumNative. Dictionary <string, bool> response = new Dictionary <string, bool>(); response.Add("updated", false); socketIOClient.Emit("getContentResponse", response); } else if (eventName == "registerDevice") { IDictionary <string, object> packetData = (IDictionary <string, object>) ((IList <object>)messageReceived[Constants.Keys.ARGS])[0]; string email = (string)packetData["email"]; LeanplumUnityHelper.QueueOnMainThread(() => { LeanplumNative.OnHasStartedAndRegisteredAsDeveloper(); LeanplumNative.CompatibilityLayer.Log( "Your device is registered to " + email + "."); }); } else if (eventName == "trigger") { IDictionary <string, object> packetData = (IDictionary <string, object>) ((IList <object>)messageReceived[Constants.Keys.ARGS])[0]; // Trigger Preview LeanplumUnityHelper.QueueOnMainThread(() => { onActionTrigger(packetData); }); } } }
/// <summary> /// Defines a new variable with a default value. If a Leanplum variable with the /// same name and type exists, this will return the existing variable. /// </summary> /// <param name="name"> Name of the variable. </param> /// <param name="defaultValue"> Default value of the variable. Can't be null. </param> public override Var <U> Define <U>(string name, U defaultValue) { string kind = null; if (defaultValue is int || defaultValue is long || defaultValue is short || defaultValue is char || defaultValue is sbyte || defaultValue is byte) { kind = Constants.Kinds.INT; } else if (defaultValue is float || defaultValue is double || defaultValue is decimal) { kind = Constants.Kinds.FLOAT; } else if (defaultValue is string) { kind = Constants.Kinds.STRING; } else if (defaultValue is IList || defaultValue is Array) { kind = Constants.Kinds.ARRAY; } else if (defaultValue is IDictionary) { kind = Constants.Kinds.DICTIONARY; } else if (defaultValue is bool) { kind = Constants.Kinds.BOOLEAN; } else { Util.MaybeThrow(new LeanplumException( "Default value for \"" + name + "\" not recognized or supported.")); return(null); } var variable = DefineHelper <U>(name, kind, defaultValue); if (variable != null) { variable.defaultClonedContainer = DeepCopyContainer(defaultValue); variable._defaultValue = defaultValue; VarCache.RegisterVariable(variable); variable.Update(); } return(variable); }
public object Traverse(string name) { if (!name.Contains('.')) { return(Util.GetValueOrDefault(vars, name)); } string[] parts = name.Split('.'); object components = vars; for (int i = 0; i < parts.Length - 1; i++) { components = VarCache.Traverse(vars, parts[i], false); } return(VarCache.Traverse(components, parts[parts.Length - 1], false)); }
internal static void TriggerPreview(IDictionary <string, object> packetData) { var actionData = Util.GetValueOrDefault(packetData, Constants.Args.ACTION) as IDictionary <string, object>; if (actionData != null) { string actionName = Util.GetValueOrDefault(actionData, Constants.Args.ACTION_NAME)?.ToString(); string messageId = Util.GetValueOrDefault(packetData, Constants.Args.MESSAGE_ID)?.ToString(); LeanplumNative.CompatibilityLayer.Log($"Preview of {actionName} Message with Id: {messageId}"); if (!string.IsNullOrWhiteSpace(actionName)) { var newVars = VarCache.MergeMessage(actionData); NativeActionContext ac = new NativeActionContext(messageId, actionName, newVars); TriggerAction(ac, newVars); } } }
private static NativeVar <U> DefineHelper <U>(string name, string kind, U defaultValue) { NativeVar <U> existing = (NativeVar <U>)VarCache.GetVariable <U>(name); if (existing != null) { existing.ClearValueChangedCallbacks(); return(existing); } // GetVariable(name) above will return null if the variable exists but of the wrong // type. Need to check if the name is not taken. if (VarCache.HasVariable(name)) { LeanplumNative.CompatibilityLayer.LogWarning("Failed to define variable: \"" + name + "\" refers to an " + "existing Leanplum variable of a different type."); return(null); } var variable = new NativeVar <U>(name, kind, defaultValue); return(variable); }
/// <summary> /// Updates the user ID and adds or modifies user attributes. /// </summary> /// <param name="newUserId">New user identifier.</param> /// <param name="value">User attributes.</param> public override void SetUserAttributes(string newUserId, IDictionary <string, object> value) { if (!calledStart) { CompatibilityLayer.LogWarning("Start was not called. Set user ID and attributes " + "as the arguments when calling Start."); return; } if (Constants.isNoop) { return; } var parameters = new Dictionary <string, string>(); if (value != null) { ValidateAttributes(value); parameters[Constants.Params.USER_ATTRIBUTES] = Json.Serialize(value); } if (!String.IsNullOrEmpty(newUserId)) { parameters[Constants.Params.NEW_USER_ID] = newUserId; VarCache.SaveDiffs(); } LeanplumRequest.Post(Constants.Methods.SET_USER_ATTRIBUTES, parameters).Send(); if (!String.IsNullOrEmpty(newUserId)) { LeanplumRequest.UserId = newUserId; if (_hasStarted) { VarCache.SaveDiffs(); } } }
public override void Update() { object newValue = VarCache.GetMergedValueFromComponentArray(NameComponents); if (newValue == null) { newValue = GetDefaultValue(); } if (Kind == Constants.Kinds.FILE) { if (VarCache.IsSilent) { return; } string newFile = newValue.ToString(); if (string.IsNullOrEmpty(newFile)) { return; } string url = null; if (VarCache.FileAttributes != null && VarCache.FileAttributes.ContainsKey(newFile)) { IDictionary <string, object> currentFile = (VarCache.FileAttributes[newFile] as IDictionary <string, object>) [string.Empty] as IDictionary <string, object>; if (currentFile.ContainsKey(Constants.Keys.URL)) { url = GetResourceURL(newFile); } } // Download new file if the file is different than: // - the current file for the varible // - the file currently being downloaded for the variable // - there is no file value and realtime updating is enabled // Do not download and update if realtime updating is disabled // Wait until the file is downloaded from the server if (currentlyDownloadingFile != newFile && !string.IsNullOrEmpty(url) && ((newFile != FileName && realtimeAssetUpdating && fileReady) || (Value == null && realtimeAssetUpdating))) { currentlyDownloadingFile = newFile; FileName = newFile; fileReady = false; void OnFileResponse(object file) { _value = (T)file; if (newFile == FileName && !fileReady) { fileReady = true; OnValueChanged(); currentlyDownloadingFile = null; } } void OnFileError(Exception ex) { if (newFile == FileName && !fileReady) { string errorMessage = $"Error downloading AssetBundle \"{Name}\" with \"{FileName}\". {ex}"; LeanplumNative.CompatibilityLayer.LogError(errorMessage); currentlyDownloadingFile = null; } } Leanplum.FileTransferManager.DownloadAssetResource(url, OnFileResponse, OnFileError); } } else { if (Json.Serialize(newValue) != Json.Serialize(Value)) { try { if (newValue is IDictionary || newValue is IList) { // If the value is a container, copy all the values from the newValue container to Value. Util.FillInValues(newValue, Value); } else { _value = (T)Convert.ChangeType(newValue, typeof(T)); } if (VarCache.IsSilent) { return; } OnValueChanged(); } catch (Exception ex) { Util.MaybeThrow(new LeanplumException("Error parsing values from server. " + ex.ToString())); return; } } } }
/// <summary> /// Forces content to update from the server. If variables have changed, the /// appropriate callbacks will fire. Use sparingly as if the app is updated, /// you'll have to deal with potentially inconsistent state or user experience. /// The provided callback will always fire regardless /// of whether the variables have changed. /// </summary> /// public override void ForceContentUpdate(Action callback) { VarCache.CheckVarsUpdate(callback); }
/// <summary> /// Call this when your application starts. /// This will initiate a call to Leanplum's servers to get the values /// of the variables used in your app. /// </summary> public override void Start(string userId, IDictionary <string, object> attributes, Leanplum.StartHandler startResponseAction) { if (calledStart) { CompatibilityLayer.Log("Already called start"); return; } if (String.IsNullOrEmpty(LeanplumRequest.AppId)) { CompatibilityLayer.LogError("You cannot call Start without setting your app's " + "API keys."); return; } if (CompatibilityLayer.GetPlatformName() == "Standalone") { // TODO: Fix this. // This is a workaround for SSL issues when running on // standalone when connecting with the production server. // No issue on staging. Constants.API_SSL = false; } if (startResponseAction != null) { Started += startResponseAction; } if (Constants.isNoop) { _hasStarted = true; startSuccessful = true; OnVariablesChanged(); OnVariablesChangedAndNoDownloadsPending(); OnStarted(true); VarCache.ApplyVariableDiffs(null, null); return; } ValidateAttributes(attributes); calledStart = true; // Load the variables that were stored on the device from the last session. VarCache.IsSilent = true; VarCache.LoadDiffs(); VarCache.IsSilent = false; // Setup class members. VarCache.Update += delegate { OnVariablesChanged(); if (LeanplumRequest.PendingDownloads == 0) { OnVariablesChangedAndNoDownloadsPending(); } }; LeanplumRequest.NoPendingDownloads += delegate { OnVariablesChangedAndNoDownloadsPending(); }; string deviceId; if (customDeviceId != null) { deviceId = customDeviceId; } else { deviceId = CompatibilityLayer.GetDeviceId(); } LeanplumRequest.DeviceId = deviceId; // Don't overwrite UserID if it was set previously if Start() // was called without a new UserID. if (!String.IsNullOrEmpty(userId)) { LeanplumRequest.UserId = userId; } if (String.IsNullOrEmpty(LeanplumRequest.UserId)) { LeanplumRequest.UserId = deviceId; } // Setup parameters. var parameters = new Dictionary <string, string>(); parameters[Constants.Params.INCLUDE_DEFAULTS] = false.ToString(); parameters[Constants.Params.VERSION_NAME] = CompatibilityLayer.VersionName ?? ""; parameters[Constants.Params.DEVICE_NAME] = CompatibilityLayer.GetDeviceName(); parameters[Constants.Params.DEVICE_MODEL] = CompatibilityLayer.GetDeviceModel(); parameters[Constants.Params.DEVICE_SYSTEM_NAME] = CompatibilityLayer.GetSystemName(); parameters[Constants.Params.DEVICE_SYSTEM_VERSION] = CompatibilityLayer.GetSystemVersion(); TimeZone timezone = System.TimeZone.CurrentTimeZone; if (timezone.IsDaylightSavingTime(DateTime.UtcNow)) { parameters[Constants.Keys.TIMEZONE] = timezone.DaylightName; } else { parameters[Constants.Keys.TIMEZONE] = timezone.StandardName; } parameters[Constants.Keys.TIMEZONE_OFFSET_SECONDS] = timezone.GetUtcOffset(DateTime.UtcNow).TotalSeconds.ToString(); parameters[Constants.Keys.COUNTRY] = Constants.Values.DETECT; parameters[Constants.Keys.REGION] = Constants.Values.DETECT; parameters[Constants.Keys.CITY] = Constants.Values.DETECT; parameters[Constants.Keys.LOCATION] = Constants.Values.DETECT; if (attributes != null) { parameters[Constants.Params.USER_ATTRIBUTES] = Json.Serialize(attributes); } // Issue start API call. LeanplumRequest req = LeanplumRequest.Post(Constants.Methods.START, parameters); req.Response += delegate(object responsesObject) { IDictionary <string, object> response = Util.GetLastResponse(responsesObject) as IDictionary <string, object>; IDictionary <string, object> values = Util.GetValueOrDefault(response, Constants.Keys.VARS) as IDictionary <string, object> ?? new Dictionary <string, object>(); IDictionary <string, object> fileAttributes = Util.GetValueOrDefault(response, Constants.Keys.FILE_ATTRIBUTES) as IDictionary <string, object> ?? new Dictionary <string, object>(); List <object> variants = Util.GetValueOrDefault(response, Constants.Keys.VARIANTS) as List <object> ?? new List <object>(); bool isRegistered = (bool)Util.GetValueOrDefault(response, Constants.Keys.IS_REGISTERED, false); LeanplumRequest.Token = Util.GetValueOrDefault(response, Constants.Keys.TOKEN) as string ?? ""; // Allow bidirectional realtime variable updates. if (Constants.isDevelopmentModeEnabled) { VarCache.SetDevModeValuesFromServer( Util.GetValueOrDefault(response, Constants.Keys.VARS_FROM_CODE) as Dictionary <string, object>); if (Constants.EnableRealtimeUpdatesInDevelopmentMode && SocketUtilsFactory.Utils.AreSocketsAvailable) { leanplumSocket = new LeanplumSocket(delegate() { // Callback when we receive an updateVars command through the // development socket. // Set a flag so that the next time VarCache.CheckVarsUpdate() is // called the variables are updated. VarCache.VarsNeedUpdate = true; }); } // Register device. if (isRegistered) { // Check for updates. string latestVersion = Util.GetValueOrDefault(response, Constants.Keys.LATEST_VERSION) as string; if (latestVersion != null) { CompatibilityLayer.Log("Leanplum Unity SDK " + latestVersion + " available. Go to https://www.leanplum.com/dashboard to " + "download it."); } OnHasStartedAndRegisteredAsDeveloper(); } } VarCache.ApplyVariableDiffs(values, fileAttributes, variants); _hasStarted = true; startSuccessful = true; OnStarted(true); CompatibilityLayer.Init(); }; req.Error += delegate { VarCache.ApplyVariableDiffs(null, null); _hasStarted = true; startSuccessful = false; OnStarted(false); CompatibilityLayer.Init(); }; req.SendIfConnected(); }
/// <summary> /// Traverses the variable structure with the specified path. /// Path components can be either strings representing keys in a dictionary, /// or integers representing indices in a list. /// </summary> public override object ObjectForKeyPathComponents(object[] pathComponents) { return(VarCache.GetMergedValueFromComponentArray(pathComponents)); }
public override void Update() { object newValue = VarCache.GetMergedValueFromComponentArray(NameComponents); if (newValue == null) { newValue = GetDefaultValue(); } if (Kind == Constants.Kinds.FILE) { if (VarCache.IsSilent) { return; } string newFile = newValue.ToString(); string url = null; if (String.IsNullOrEmpty(newFile)) { return; } if (VarCache.FileAttributes != null && VarCache.FileAttributes.ContainsKey(newFile)) { IDictionary <string, object> currentFile = (VarCache.FileAttributes[newFile] as IDictionary <string, object>) [String.Empty] as IDictionary <string, object>; if (currentFile.ContainsKey(Constants.Keys.URL)) { url = ((VarCache.FileAttributes[newFile] as IDictionary <string, object>) [String.Empty] as IDictionary <string, object>)[Constants.Keys.URL] as string; } } // Update if the file is different from what we currently have and if we have started downloading it // already. Also update if we don't have the file but don't update if realtime updating is disabled - // wait 'til we get the value from the serve so that the correct file is displayed. if (currentlyDownloadingFile != newFile && !String.IsNullOrEmpty(url) && ((newFile != FileName && realtimeAssetUpdating && fileReady) || (Value == null && realtimeAssetUpdating))) { VarCache.downloadsPending++; currentlyDownloadingFile = newFile; FileName = newFile; fileReady = false; LeanplumRequest downloadRequest = LeanplumRequest.Get(url.Substring(1)); downloadRequest.Response += delegate(object obj) { _value = (T)obj; if (newFile == FileName && !fileReady) { fileReady = true; OnValueChanged(); currentlyDownloadingFile = null; } VarCache.downloadsPending--; }; downloadRequest.Error += delegate(Exception obj) { if (newFile == FileName && !fileReady) { LeanplumNative.CompatibilityLayer.LogError("Error downloading assetbundle \"" + FileName + "\". " + obj.ToString()); currentlyDownloadingFile = null; } VarCache.downloadsPending--; }; downloadRequest.DownloadAssetNow(); } } else { if (Json.Serialize(newValue) != Json.Serialize(Value)) { try { if (newValue is IDictionary || newValue is IList) { // If the value is a container, copy all the values from the newValue container to Value. SharedUtil.FillInValues(newValue, Value); } else { _value = (T)Convert.ChangeType(newValue, typeof(T)); } if (VarCache.IsSilent) { return; } OnValueChanged(); } catch (Exception ex) { Util.MaybeThrow(new LeanplumException("Error parsing values from server. " + ex.ToString())); return; } } } }
public override void ForceSyncVariables(Leanplum.SyncVariablesCompleted completedHandler) { VarCache.ForceSendVariables(completedHandler); }