com.amanotes.gdk 0.2.13 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/Editor/EmbedRemoteConfigAssetInspector.cs +191 -0
- package/Editor/EmbedRemoteConfigAssetInspector.cs.meta +11 -0
- package/Editor/Utils/AmaGDKEditor.Utils.cs +49 -0
- package/Extra/AmaGDKInstaller.unitypackage +0 -0
- package/Extra/AmaGDKProject.unitypackage +0 -0
- package/Packages/AdjustAdapter_v4.33.0.unitypackage +0 -0
- package/Packages/AmaGDKConfig.unitypackage +0 -0
- package/Packages/AmaGDKExample.unitypackage +0 -0
- package/Packages/AmaGDKTest.unitypackage +0 -0
- package/Packages/AmaPassport.ATTSupport.unitypackage +0 -0
- package/Packages/AmaPassportAdapter_v1.0.0.unitypackage +0 -0
- package/Packages/AppsFlyerAdapter_v6.5.4.unitypackage +0 -0
- package/Packages/FirebaseAnalyticsAdapter_v9.1.0.unitypackage +0 -0
- package/Packages/FirebaseRemoteConfigAdapter_v9.1.0.unitypackage +0 -0
- package/Packages/IronsourceAdapter_v7.2.6.unitypackage +0 -0
- package/Runtime/AmaGDK.Analytics.cs +22 -3
- package/Runtime/AmaGDK.RemoteConfig.cs +73 -43
- package/Runtime/AmaGDK.cs +1 -1
- package/Runtime/AnalyticQualityAsset.cs +218 -0
- package/Runtime/AnalyticQualityAsset.cs.meta +11 -0
- package/Runtime/EmbedRemoteConfigAsset.cs +59 -0
- package/Runtime/EmbedRemoteConfigAsset.cs.meta +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.14 - 2023-08-08]
|
|
4
|
+
### Support multiple embed remote config and load config by id
|
|
5
|
+
### Clear cached remote config on new app update
|
|
6
|
+
### Override remote config value in editor mode
|
|
7
|
+
### New feature: Analytics Quality
|
|
8
|
+
|
|
3
9
|
## [0.2.13 - 2023-07-20]
|
|
4
10
|
### Fix error Remote Config JSON caused by special chars (", ', \)
|
|
5
11
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Text;
|
|
3
|
+
using Amanotes.Core.Internal;
|
|
4
|
+
using UnityEditor;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
|
|
7
|
+
using static Amanotes.Core.Internal.Logging;
|
|
8
|
+
|
|
9
|
+
namespace Amanotes.Editor
|
|
10
|
+
{
|
|
11
|
+
[CustomEditor(typeof(EmbedRemoteConfigAsset))]
|
|
12
|
+
internal class EmbedRemoteConfigAssetInspector : UnityEditor.Editor
|
|
13
|
+
{
|
|
14
|
+
private EmbedRemoteConfigAsset remoteConfigAsset;
|
|
15
|
+
private SerializedProperty overrideInEditorProperty;
|
|
16
|
+
private SerializedProperty overrideConfigProperty;
|
|
17
|
+
private SerializedProperty configsProperty;
|
|
18
|
+
private bool[] foldouts;
|
|
19
|
+
|
|
20
|
+
private void OnEnable()
|
|
21
|
+
{
|
|
22
|
+
remoteConfigAsset = (EmbedRemoteConfigAsset)target;
|
|
23
|
+
overrideInEditorProperty = serializedObject.FindProperty("overrideInEditor");
|
|
24
|
+
overrideConfigProperty = serializedObject.FindProperty("overrideConfig");
|
|
25
|
+
configsProperty = serializedObject.FindProperty("configs");
|
|
26
|
+
foldouts = new bool[configsProperty.arraySize];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public override void OnInspectorGUI()
|
|
30
|
+
{
|
|
31
|
+
EditorGUILayout.BeginHorizontal();
|
|
32
|
+
EditorGUILayout.LabelField("Active Id:");
|
|
33
|
+
EditorGUILayout.LabelField(remoteConfigAsset.activeId);
|
|
34
|
+
EditorGUILayout.EndHorizontal();
|
|
35
|
+
|
|
36
|
+
serializedObject.Update();
|
|
37
|
+
|
|
38
|
+
DrawPropertiesExcluding(serializedObject, "m_Script", "activeId", "configs", "overrideConfig");
|
|
39
|
+
|
|
40
|
+
if (overrideInEditorProperty.boolValue)
|
|
41
|
+
{
|
|
42
|
+
EditorGUILayout.PropertyField(overrideConfigProperty);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (GUILayout.Button("Add a config set"))
|
|
46
|
+
{
|
|
47
|
+
configsProperty.arraySize++;
|
|
48
|
+
int lastIndex = configsProperty.arraySize - 1;
|
|
49
|
+
var config= configsProperty.GetArrayElementAtIndex(lastIndex);
|
|
50
|
+
config.FindPropertyRelative("id").stringValue = $"config{lastIndex}";
|
|
51
|
+
foldouts = AddElementToArray(foldouts, false);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (int i = 0; i < configsProperty.arraySize; i++)
|
|
55
|
+
{
|
|
56
|
+
SerializedProperty configProperty = configsProperty.GetArrayElementAtIndex(i);
|
|
57
|
+
SerializedProperty idProperty = configProperty.FindPropertyRelative("id");
|
|
58
|
+
SerializedProperty fetchUrlProperty = configProperty.FindPropertyRelative("fetchUrl");
|
|
59
|
+
SerializedProperty itemsProperty = configProperty.FindPropertyRelative("items");
|
|
60
|
+
|
|
61
|
+
EditorGUILayout.BeginVertical(GUI.skin.box);
|
|
62
|
+
foldouts[i] = EditorGUILayout.Foldout(foldouts[i], idProperty.stringValue);
|
|
63
|
+
if (foldouts[i])
|
|
64
|
+
{
|
|
65
|
+
EditorGUI.indentLevel++;
|
|
66
|
+
EditorGUILayout.BeginHorizontal();
|
|
67
|
+
if (GUILayout.Button("Set active", GUILayout.MinWidth(100)))
|
|
68
|
+
{
|
|
69
|
+
remoteConfigAsset.SetActiveConfig(idProperty.stringValue);
|
|
70
|
+
}
|
|
71
|
+
if (GUILayout.Button("Fetch", GUILayout.MinWidth(80)))
|
|
72
|
+
{
|
|
73
|
+
var url = fetchUrlProperty.stringValue;
|
|
74
|
+
var currentConfigIndex = i;
|
|
75
|
+
if (string.IsNullOrWhiteSpace(url))
|
|
76
|
+
{
|
|
77
|
+
ShowError("Please fill the fetch URL first");
|
|
78
|
+
}
|
|
79
|
+
else
|
|
80
|
+
{
|
|
81
|
+
WebRequest.Get(url, onProgress: FetchProgress, onSuccess: s => FillEmbedConfig(currentConfigIndex, s), onError:ShowError);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (GUILayout.Button("Remove", GUILayout.MinWidth(80)))
|
|
85
|
+
{
|
|
86
|
+
if (remoteConfigAsset.ActiveConfig?.id == idProperty.stringValue)
|
|
87
|
+
{
|
|
88
|
+
remoteConfigAsset.SetActiveConfig(string.Empty);
|
|
89
|
+
}
|
|
90
|
+
configsProperty.DeleteArrayElementAtIndex(i);
|
|
91
|
+
foldouts = RemoveElementAtIndex(foldouts, i);
|
|
92
|
+
}
|
|
93
|
+
if (GUILayout.Button("Copy", GUILayout.MinWidth(80)))
|
|
94
|
+
{
|
|
95
|
+
GUIUtility.systemCopyBuffer = KeyValueToText(itemsProperty);
|
|
96
|
+
}
|
|
97
|
+
if (GUILayout.Button("Paste", GUILayout.MinWidth(80)))
|
|
98
|
+
{
|
|
99
|
+
FillEmbedConfig(i, GUIUtility.systemCopyBuffer);
|
|
100
|
+
}
|
|
101
|
+
EditorGUILayout.EndHorizontal();
|
|
102
|
+
EditorGUILayout.PropertyField(idProperty);
|
|
103
|
+
EditorGUILayout.PropertyField(fetchUrlProperty);
|
|
104
|
+
EditorGUILayout.PropertyField(itemsProperty);
|
|
105
|
+
|
|
106
|
+
EditorGUI.indentLevel--;
|
|
107
|
+
}
|
|
108
|
+
EditorGUILayout.EndVertical();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
serializedObject.ApplyModifiedProperties();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private T[] AddElementToArray<T>(T[] array, T element)
|
|
115
|
+
{
|
|
116
|
+
T[] newArray = new T[array.Length + 1];
|
|
117
|
+
array.CopyTo(newArray, 0);
|
|
118
|
+
newArray[array.Length] = element;
|
|
119
|
+
return newArray;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private T[] RemoveElementAtIndex<T>(T[] array, int index)
|
|
123
|
+
{
|
|
124
|
+
T[] newArray = new T[array.Length - 1];
|
|
125
|
+
for (int i = 0, j = 0; i < array.Length; i++)
|
|
126
|
+
{
|
|
127
|
+
if (i != index)
|
|
128
|
+
{
|
|
129
|
+
newArray[j] = array[i];
|
|
130
|
+
j++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return newArray;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private void FetchProgress(float progress)
|
|
137
|
+
{
|
|
138
|
+
EditorUtility.DisplayProgressBar("Fetching default remote config", "Please wait...", progress);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private void FillEmbedConfig(int configIndex, string rawText)
|
|
142
|
+
{
|
|
143
|
+
try
|
|
144
|
+
{
|
|
145
|
+
string[] lines = rawText.Split("\n");
|
|
146
|
+
KeyValue[] arrKeyValue = new KeyValue[lines.Length];
|
|
147
|
+
for (int i = 0; i < lines.Length; i++)
|
|
148
|
+
{
|
|
149
|
+
var line = lines[i];
|
|
150
|
+
var idx = line.IndexOf('\t');
|
|
151
|
+
if (idx == -1)
|
|
152
|
+
{
|
|
153
|
+
LogWarning($"[RemoteConfig] Cannot get (key, value) at line {i+1}. Content:\n<{line}>");
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
arrKeyValue[i] = new KeyValue
|
|
157
|
+
{
|
|
158
|
+
key = line.Substring(0, idx),
|
|
159
|
+
value = line.Substring(idx + 1)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
remoteConfigAsset.configs[configIndex].items = arrKeyValue;
|
|
163
|
+
}
|
|
164
|
+
catch (Exception e)
|
|
165
|
+
{
|
|
166
|
+
LogWarning(e.Message);
|
|
167
|
+
}
|
|
168
|
+
finally
|
|
169
|
+
{
|
|
170
|
+
EditorUtility.ClearProgressBar();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private string KeyValueToText(SerializedProperty items)
|
|
175
|
+
{
|
|
176
|
+
StringBuilder sb = new StringBuilder();
|
|
177
|
+
for (int i = 0; i < items.arraySize; i++)
|
|
178
|
+
{
|
|
179
|
+
SerializedProperty c = items.GetArrayElementAtIndex(i);
|
|
180
|
+
sb.AppendLine($"{c.FindPropertyRelative("key").stringValue}\t{c.FindPropertyRelative("value").stringValue}");
|
|
181
|
+
}
|
|
182
|
+
return sb.ToString();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private void ShowError(string error)
|
|
186
|
+
{
|
|
187
|
+
EditorUtility.ClearProgressBar();
|
|
188
|
+
EditorUtility.DisplayDialog("Error", error, "OK");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
using System;
|
|
2
2
|
using System.Collections;
|
|
3
|
+
using System.Collections.Generic;
|
|
3
4
|
using UnityEditor;
|
|
4
5
|
using UnityEngine;
|
|
6
|
+
using UnityEngine.Networking;
|
|
7
|
+
|
|
5
8
|
namespace Amanotes.Editor
|
|
6
9
|
{
|
|
7
10
|
internal static class Utils
|
|
@@ -31,4 +34,50 @@ namespace Amanotes.Editor
|
|
|
31
34
|
EditorApplication.update += closureCallback;
|
|
32
35
|
}
|
|
33
36
|
}
|
|
37
|
+
|
|
38
|
+
internal static class WebRequest
|
|
39
|
+
{
|
|
40
|
+
internal static void Get(string url, Dictionary<string, string> headers = null, Action<float> onProgress = null, Action<string> onSuccess = null, Action<string> onError = null)
|
|
41
|
+
{
|
|
42
|
+
GetCoroutine(url, headers, onProgress, onSuccess, onError).StartEditorCoroutine();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private static IEnumerator GetCoroutine(string url, Dictionary<string, string> headers = null, Action<float> onProgress = null, Action<string> onSuccess = null, Action<string> onError = null)
|
|
46
|
+
{
|
|
47
|
+
using var request = UnityWebRequest.Get(url);
|
|
48
|
+
// Add headers to the request
|
|
49
|
+
if (headers != null)
|
|
50
|
+
{
|
|
51
|
+
foreach (var header in headers)
|
|
52
|
+
{
|
|
53
|
+
request.SetRequestHeader(header.Key, header.Value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Send the request
|
|
58
|
+
request.timeout = 60;
|
|
59
|
+
yield return request.SendWebRequest();
|
|
60
|
+
|
|
61
|
+
while (request.isDone == false)
|
|
62
|
+
{
|
|
63
|
+
onProgress?.Invoke(request.downloadProgress);
|
|
64
|
+
yield return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check for errors
|
|
68
|
+
#pragma warning disable CS0618
|
|
69
|
+
if (request.isNetworkError || request.isHttpError)
|
|
70
|
+
#pragma warning restore CS0618
|
|
71
|
+
{
|
|
72
|
+
Debug.LogError("Error: " + request.error);
|
|
73
|
+
onError?.Invoke(request.error);
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
{
|
|
77
|
+
// Process the response
|
|
78
|
+
string responseText = request.downloadHandler.text;
|
|
79
|
+
onSuccess?.Invoke(responseText);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
34
83
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -301,7 +301,8 @@ namespace Amanotes.Core
|
|
|
301
301
|
internal static void ProcessEventQueue()
|
|
302
302
|
{
|
|
303
303
|
if (eventQueue.Count <= 0) return;
|
|
304
|
-
|
|
304
|
+
SDKConfig common = Config.common;
|
|
305
|
+
bool showAnalyticsLog = common.showAnalyticsLog;
|
|
305
306
|
sessionStat.ClearLastFrameEventInfo();
|
|
306
307
|
|
|
307
308
|
while (eventQueue.Count > 0)
|
|
@@ -317,9 +318,11 @@ namespace Amanotes.Core
|
|
|
317
318
|
}
|
|
318
319
|
|
|
319
320
|
string eventNameToSend = GetEventNameToSend(eventData.eventName, countInSession, totalCount);
|
|
320
|
-
if (
|
|
321
|
+
if (common.normalizeEventName) NormalizeEventName(ref eventNameToSend);
|
|
321
322
|
|
|
322
|
-
|
|
323
|
+
if (showAnalyticsLog) Debug.Log($"AmaGDK | Event <{eventNameToSend}> (in session: {countInSession}, total: {totalCount})\n{JsonUtils.DictionaryToJson(eventData.parameters, true)}");
|
|
324
|
+
|
|
325
|
+
common.quality.CheckEventQuality(eventData.eventName, eventData.parameters);
|
|
323
326
|
|
|
324
327
|
var inUseAdapterIDs = new List<string>();
|
|
325
328
|
foreach (AdapterContext m in listAdapters)
|
|
@@ -731,12 +734,28 @@ namespace Amanotes.Core
|
|
|
731
734
|
|
|
732
735
|
namespace Amanotes.Core.Internal
|
|
733
736
|
{
|
|
737
|
+
[Serializable]
|
|
738
|
+
public class AnalyticQualityConfig
|
|
739
|
+
{
|
|
740
|
+
public bool enable = false;
|
|
741
|
+
public AnalyticQualityAsset qualityAsset;
|
|
742
|
+
|
|
743
|
+
public void CheckEventQuality(string eventName, Dictionary<string, object> parameters)
|
|
744
|
+
{
|
|
745
|
+
if (enable && qualityAsset != null)
|
|
746
|
+
{
|
|
747
|
+
qualityAsset.CheckEventQuality(eventName, parameters);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
734
752
|
public partial class SDKConfig
|
|
735
753
|
{
|
|
736
754
|
public bool showAnalyticsLog = true;
|
|
737
755
|
public bool normalizeEventName = true;
|
|
738
756
|
public bool checkMigrationIntegrity = true;
|
|
739
757
|
public bool enableEventStat;
|
|
758
|
+
public AnalyticQualityConfig quality;
|
|
740
759
|
}
|
|
741
760
|
|
|
742
761
|
public interface IAnalyticAdapter
|
|
@@ -16,8 +16,7 @@ namespace Amanotes.Core
|
|
|
16
16
|
{
|
|
17
17
|
public enum RemoteConfigSource
|
|
18
18
|
{
|
|
19
|
-
|
|
20
|
-
DefaultFile,
|
|
19
|
+
EmbedConfig,
|
|
21
20
|
Cache,
|
|
22
21
|
Internet
|
|
23
22
|
}
|
|
@@ -26,7 +25,7 @@ namespace Amanotes.Core
|
|
|
26
25
|
public partial class RemoteConfig //Public
|
|
27
26
|
{
|
|
28
27
|
public static Action<bool> onFetchCompleted;
|
|
29
|
-
public static Dictionary<string, string> DictConfig
|
|
28
|
+
public static Dictionary<string, string> DictConfig => dictConfig;
|
|
30
29
|
public static RemoteConfigSource Source { private set; get; }
|
|
31
30
|
static bool? _cacheExisted = null;
|
|
32
31
|
public static bool HasCache
|
|
@@ -64,6 +63,15 @@ namespace Amanotes.Core
|
|
|
64
63
|
|
|
65
64
|
public static T Get<T>(string key, T defaultValue = default(T))
|
|
66
65
|
{
|
|
66
|
+
#if UNITY_EDITOR
|
|
67
|
+
//Override value in editor environment
|
|
68
|
+
if (adapterConfig.embedRemoteConfig.overrideInEditor)
|
|
69
|
+
{
|
|
70
|
+
foreach (var c in adapterConfig.embedRemoteConfig.overrideConfig)
|
|
71
|
+
if (c.key == key)
|
|
72
|
+
return (T)Convert.ChangeType(c.value, typeof(T));
|
|
73
|
+
}
|
|
74
|
+
#endif
|
|
67
75
|
if (dictConfig.ContainsKey(key))
|
|
68
76
|
return (T)Convert.ChangeType(dictConfig[key], typeof(T));
|
|
69
77
|
return defaultValue;
|
|
@@ -78,6 +86,13 @@ namespace Amanotes.Core
|
|
|
78
86
|
{
|
|
79
87
|
return JsonUtils.DictionaryToJson(dictConfig);
|
|
80
88
|
}
|
|
89
|
+
|
|
90
|
+
public static void SetActiveEmbedConfig(string id)
|
|
91
|
+
{
|
|
92
|
+
if (adapterConfig == null || adapterConfig.embedRemoteConfig == null)
|
|
93
|
+
LogWarning("[RemoteConfig] No embed remote config available");
|
|
94
|
+
adapterConfig.embedRemoteConfig.SetActiveConfig(id);
|
|
95
|
+
}
|
|
81
96
|
}
|
|
82
97
|
|
|
83
98
|
public partial class RemoteConfig //Internal
|
|
@@ -91,7 +106,7 @@ namespace Amanotes.Core
|
|
|
91
106
|
SaveCache,
|
|
92
107
|
|
|
93
108
|
LoadCache,
|
|
94
|
-
|
|
109
|
+
LoadEmbedConfig,
|
|
95
110
|
|
|
96
111
|
Success,
|
|
97
112
|
Fail
|
|
@@ -100,9 +115,15 @@ namespace Amanotes.Core
|
|
|
100
115
|
static Dictionary<string, string> dictConfig = new Dictionary<string, string>();
|
|
101
116
|
static string fileName = $"{nameof(RemoteConfig)}.kv";
|
|
102
117
|
static string base64Prefix = "base64:";
|
|
118
|
+
const string VERSION_PREFIX = "version:";
|
|
119
|
+
static int EmbedVersion =>
|
|
120
|
+
adapterConfig != null && adapterConfig.embedRemoteConfig != null
|
|
121
|
+
? adapterConfig.embedRemoteConfig.version
|
|
122
|
+
: 1;
|
|
103
123
|
|
|
104
124
|
static FetchState _state = FetchState.None;
|
|
105
|
-
internal static FetchState State
|
|
125
|
+
internal static FetchState State => _state;
|
|
126
|
+
|
|
106
127
|
static readonly Dictionary<FetchState, Action[]> fetchSM = new Dictionary<FetchState, Action[]>
|
|
107
128
|
{
|
|
108
129
|
{ FetchState.None, new Action[]{ ResolveDependency }},
|
|
@@ -110,8 +131,8 @@ namespace Amanotes.Core
|
|
|
110
131
|
{ FetchState.CheckInternet, new Action[]{ LoadCache, Fetch}},
|
|
111
132
|
{ FetchState.Fetch, new Action[]{ LoadCache, SaveCache}},
|
|
112
133
|
{ FetchState.SaveCache, new Action[]{ SetDone }},
|
|
113
|
-
{ FetchState.LoadCache, new Action[]{
|
|
114
|
-
{ FetchState.
|
|
134
|
+
{ FetchState.LoadCache, new Action[]{ LoadEmbedConfig, SetDone}},
|
|
135
|
+
{ FetchState.LoadEmbedConfig, new Action[]{ SetFail, SetDone}}
|
|
115
136
|
};
|
|
116
137
|
|
|
117
138
|
internal static IRemoteConfig adapter;
|
|
@@ -177,27 +198,14 @@ namespace Amanotes.Core
|
|
|
177
198
|
{
|
|
178
199
|
_state = FetchState.SaveCache;
|
|
179
200
|
StringBuilder sb = new StringBuilder();
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
bool needUpdateDefault = Application.isEditor && adapterConfig.autoUpdateDefaultValue;
|
|
183
|
-
List<KeyValue> list = needUpdateDefault ? new List<KeyValue>() : null;
|
|
184
201
|
|
|
202
|
+
//Add embedded remote config version to force load from embedded config after app update
|
|
203
|
+
sb.AppendLine($"{VERSION_PREFIX}{EmbedVersion}");
|
|
204
|
+
|
|
185
205
|
foreach (var c in dictConfig)
|
|
186
206
|
{
|
|
187
207
|
sb.AppendLine($"{c.Key}\t{Encode(c.Value)}");
|
|
188
|
-
if (!needUpdateDefault) continue;
|
|
189
|
-
list.Add(new KeyValue() { key = c.Key, value = c.Value });
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
#if UNITY_EDITOR
|
|
193
|
-
if (needUpdateDefault)
|
|
194
|
-
{
|
|
195
|
-
list.Sort((item1, item2) => string.Compare(item1.key, item2.key, StringComparison.Ordinal));
|
|
196
|
-
adapterConfig.defaultValue = list.ToArray();
|
|
197
|
-
EditorUtility.SetDirty(_config);
|
|
198
|
-
Log($"[RemoteConfig] Updated default remote config!");
|
|
199
208
|
}
|
|
200
|
-
#endif
|
|
201
209
|
|
|
202
210
|
string filePath = GDKFileUtils.GetPath(fileName);
|
|
203
211
|
GDKFileUtils.Save(filePath, sb.ToString());
|
|
@@ -220,15 +228,32 @@ namespace Amanotes.Core
|
|
|
220
228
|
dictConfig.Clear();
|
|
221
229
|
using (StreamReader sr = new StreamReader(GDKFileUtils.GetPath(fileName)))
|
|
222
230
|
{
|
|
231
|
+
//Check new app update
|
|
232
|
+
string firstLine = sr.ReadLine();
|
|
233
|
+
if (firstLine == null)
|
|
234
|
+
{
|
|
235
|
+
NextState(false);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (ShouldClearCache(firstLine))
|
|
239
|
+
{
|
|
240
|
+
GDKFileUtils.Delete(fileName);
|
|
241
|
+
string oldVersion = firstLine.StartsWith(VERSION_PREFIX) ? firstLine : "<none>";
|
|
242
|
+
Log($"[RemoteConfig] Cached remote config cleared due to embed version changed:\n{oldVersion} -> {EmbedVersion}");
|
|
243
|
+
NextState(false);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
//Read key-value
|
|
223
248
|
string line;
|
|
224
|
-
int
|
|
249
|
+
int lineNo = 1;
|
|
225
250
|
while ((line = sr.ReadLine()) != null)
|
|
226
251
|
{
|
|
227
|
-
|
|
252
|
+
lineNo++;
|
|
228
253
|
var idx = line.IndexOf('\t');
|
|
229
254
|
if (idx == -1)
|
|
230
255
|
{
|
|
231
|
-
LogWarning($"[RemoteConfig] Cannot get (key, value) at line {
|
|
256
|
+
LogWarning($"[RemoteConfig] Cannot get (key, value) at line {lineNo} of cache. Content:\n<{line}>");
|
|
232
257
|
continue;
|
|
233
258
|
}
|
|
234
259
|
string key = line.Substring(0, idx);
|
|
@@ -250,24 +275,30 @@ namespace Amanotes.Core
|
|
|
250
275
|
NextState(true);
|
|
251
276
|
}
|
|
252
277
|
|
|
253
|
-
static void
|
|
278
|
+
static void LoadEmbedConfig()
|
|
254
279
|
{
|
|
255
|
-
_state = FetchState.
|
|
280
|
+
_state = FetchState.LoadEmbedConfig;
|
|
281
|
+
|
|
282
|
+
if (adapterConfig == null || adapterConfig.embedRemoteConfig == null)
|
|
283
|
+
{
|
|
284
|
+
NextState(false);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
256
287
|
|
|
257
|
-
var
|
|
288
|
+
var config = adapterConfig.embedRemoteConfig.ActiveConfig;
|
|
258
289
|
|
|
259
|
-
if (
|
|
290
|
+
if (config == null || config.items.Length == 0)
|
|
260
291
|
{
|
|
261
292
|
NextState(false);
|
|
262
293
|
return;
|
|
263
294
|
}
|
|
264
295
|
|
|
265
|
-
foreach (var item in
|
|
296
|
+
foreach (var item in config.items)
|
|
266
297
|
{
|
|
267
298
|
dictConfig.Add(item.key, item.value);
|
|
268
299
|
}
|
|
269
|
-
Source = RemoteConfigSource.
|
|
270
|
-
Log("[RemoteConfig] Load from
|
|
300
|
+
Source = RemoteConfigSource.EmbedConfig;
|
|
301
|
+
Log("[RemoteConfig] Load from embed asset");
|
|
271
302
|
NextState(true);
|
|
272
303
|
}
|
|
273
304
|
|
|
@@ -301,6 +332,12 @@ namespace Amanotes.Core
|
|
|
301
332
|
s = s.Remove(0, base64Prefix.Length);
|
|
302
333
|
return Encoding.UTF8.GetString(Convert.FromBase64String(s));
|
|
303
334
|
}
|
|
335
|
+
|
|
336
|
+
static bool ShouldClearCache(string cacheVersion)
|
|
337
|
+
{
|
|
338
|
+
if (!cacheVersion.StartsWith(VERSION_PREFIX)) return true;
|
|
339
|
+
return cacheVersion != VERSION_PREFIX + EmbedVersion;
|
|
340
|
+
}
|
|
304
341
|
}
|
|
305
342
|
}
|
|
306
343
|
|
|
@@ -324,19 +361,12 @@ namespace Amanotes.Core.Internal
|
|
|
324
361
|
[Serializable]
|
|
325
362
|
public class RemoteConfigConfig
|
|
326
363
|
{
|
|
327
|
-
|
|
328
|
-
|
|
364
|
+
[SerializeField]
|
|
365
|
+
internal EmbedRemoteConfigAsset embedRemoteConfig;
|
|
329
366
|
public float firstFetchTimeOutInSeconds = 10;
|
|
330
367
|
public float defaultTimeOutInSeconds = 3;
|
|
331
368
|
}
|
|
332
|
-
|
|
333
|
-
[Serializable]
|
|
334
|
-
public class KeyValue
|
|
335
|
-
{
|
|
336
|
-
public string key;
|
|
337
|
-
public string value;
|
|
338
|
-
}
|
|
339
|
-
|
|
369
|
+
|
|
340
370
|
public interface IRemoteConfig
|
|
341
371
|
{
|
|
342
372
|
void ResolveDependencies(Action<bool> onComplete);
|
package/Runtime/AmaGDK.cs
CHANGED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Text;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
using static Amanotes.Core.Internal.Logging;
|
|
7
|
+
#if UNITY_EDITOR
|
|
8
|
+
using UnityEditor;
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
namespace Amanotes.Core
|
|
12
|
+
{
|
|
13
|
+
public enum ParamType
|
|
14
|
+
{
|
|
15
|
+
String,
|
|
16
|
+
Integer,
|
|
17
|
+
RealNumber
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[Serializable]
|
|
21
|
+
public class EventParam
|
|
22
|
+
{
|
|
23
|
+
public string paramName;
|
|
24
|
+
public ParamType type;
|
|
25
|
+
public bool isOptional;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[Serializable]
|
|
29
|
+
public class EventQuality
|
|
30
|
+
{
|
|
31
|
+
public string eventName;
|
|
32
|
+
public List<EventParam> lstParam;
|
|
33
|
+
|
|
34
|
+
private Dictionary<string, EventParam> dicParam = null;
|
|
35
|
+
|
|
36
|
+
internal Dictionary<string, EventParam> DicParam
|
|
37
|
+
{
|
|
38
|
+
get
|
|
39
|
+
{
|
|
40
|
+
if (dicParam == null) BuildCache();
|
|
41
|
+
return dicParam;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private void BuildCache()
|
|
46
|
+
{
|
|
47
|
+
dicParam = new Dictionary<string, EventParam>();
|
|
48
|
+
foreach (var param in lstParam)
|
|
49
|
+
{
|
|
50
|
+
if (dicParam.ContainsKey(param.paramName)) continue;
|
|
51
|
+
|
|
52
|
+
dicParam.Add(param.paramName, param);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private bool IsNumericType(Type type)
|
|
57
|
+
{
|
|
58
|
+
return type == typeof(int) ||
|
|
59
|
+
type == typeof(long) ||
|
|
60
|
+
type == typeof(short) ||
|
|
61
|
+
type == typeof(byte) ||
|
|
62
|
+
type == typeof(double) ||
|
|
63
|
+
type == typeof(float) ||
|
|
64
|
+
type == typeof(decimal) ||
|
|
65
|
+
type == typeof(uint) ||
|
|
66
|
+
type == typeof(ulong) ||
|
|
67
|
+
type == typeof(ushort) ||
|
|
68
|
+
type == typeof(sbyte);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private bool IsIntegerType(Type type)
|
|
72
|
+
{
|
|
73
|
+
return type == typeof(sbyte) ||
|
|
74
|
+
type == typeof(byte) ||
|
|
75
|
+
type == typeof(short) ||
|
|
76
|
+
type == typeof(ushort) ||
|
|
77
|
+
type == typeof(int) ||
|
|
78
|
+
type == typeof(uint) ||
|
|
79
|
+
type == typeof(long) ||
|
|
80
|
+
type == typeof(ulong);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
internal void ValidateParams(string eventName, Dictionary<string, object> eventParams)
|
|
84
|
+
{
|
|
85
|
+
StringBuilder logBuilder = new StringBuilder();
|
|
86
|
+
logBuilder.AppendLine($"Analytics quality violation found for event <{eventName}>");
|
|
87
|
+
bool isValid = true;
|
|
88
|
+
HashSet<string> checkedParameters = new HashSet<string>();
|
|
89
|
+
|
|
90
|
+
foreach (var kvp in DicParam)
|
|
91
|
+
{
|
|
92
|
+
if (!eventParams.ContainsKey(kvp.Key))
|
|
93
|
+
{
|
|
94
|
+
if (!kvp.Value.isOptional)
|
|
95
|
+
{
|
|
96
|
+
isValid = false;
|
|
97
|
+
logBuilder.AppendLine($"[-] {kvp.Key}");
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
object paramValue = eventParams[kvp.Key];
|
|
103
|
+
|
|
104
|
+
if (!ValidateParamType(paramValue, kvp.Value.type))
|
|
105
|
+
{
|
|
106
|
+
isValid = false;
|
|
107
|
+
logBuilder.AppendLine($"[X] {kvp.Key}. Input type {paramValue.GetType()}, Expected type: {kvp.Value.type.ToString()}.");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
checkedParameters.Add(kvp.Key);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
foreach (var parameter in eventParams)
|
|
114
|
+
{
|
|
115
|
+
if (checkedParameters.Contains(parameter.Key)) continue;
|
|
116
|
+
|
|
117
|
+
isValid = false;
|
|
118
|
+
logBuilder.AppendLine($"[+] {parameter.Key}");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!isValid) LogWarningOnce(logBuilder.ToString());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private bool ValidateParamType(object paramValue, ParamType paramType)
|
|
125
|
+
{
|
|
126
|
+
bool isValidType = false;
|
|
127
|
+
|
|
128
|
+
switch (paramType)
|
|
129
|
+
{
|
|
130
|
+
case ParamType.String:
|
|
131
|
+
isValidType = paramValue is string;
|
|
132
|
+
break;
|
|
133
|
+
case ParamType.Integer:
|
|
134
|
+
isValidType = IsIntegerType(paramValue.GetType());
|
|
135
|
+
break;
|
|
136
|
+
case ParamType.RealNumber:
|
|
137
|
+
isValidType = IsNumericType(paramValue.GetType());
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
LogWarningOnce($"Unsupported EventParam type: + {paramType}");
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return isValidType;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
[CreateAssetMenu(fileName = "AmaGDKAnalyticsQuality", menuName = "Ama GDK/Analytics Quality", order = 3)]
|
|
149
|
+
[Serializable]
|
|
150
|
+
public class AnalyticQualityAsset : ScriptableObject
|
|
151
|
+
{
|
|
152
|
+
[SerializeField] private List<EventQuality> lstEvent = new List<EventQuality>();
|
|
153
|
+
|
|
154
|
+
private Dictionary<string, EventQuality> dictEvent = null;
|
|
155
|
+
private void BuildCache()
|
|
156
|
+
{
|
|
157
|
+
dictEvent = new Dictionary<string, EventQuality>();
|
|
158
|
+
foreach (var evt in lstEvent)
|
|
159
|
+
{
|
|
160
|
+
if (dictEvent.ContainsKey(evt.eventName))
|
|
161
|
+
{
|
|
162
|
+
LogWarningOnce($"Duplicate event: {evt.eventName}");
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
dictEvent.Add(evt.eventName, evt);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private Dictionary<string, EventQuality> DictEvent
|
|
171
|
+
{
|
|
172
|
+
get
|
|
173
|
+
{
|
|
174
|
+
if (dictEvent == null) BuildCache();
|
|
175
|
+
return dictEvent;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
#if UNITY_EDITOR
|
|
179
|
+
[ContextMenu("Validate Unique EventNames")]
|
|
180
|
+
private void ValidateUniqueEventNames()
|
|
181
|
+
{
|
|
182
|
+
HashSet<string> eventNames = new HashSet<string>();
|
|
183
|
+
|
|
184
|
+
lstEvent = lstEvent.OrderBy(evt => evt.eventName).ToList();
|
|
185
|
+
|
|
186
|
+
foreach (EventQuality eventQuality in lstEvent)
|
|
187
|
+
{
|
|
188
|
+
if (string.IsNullOrWhiteSpace(eventQuality.eventName)) Debug.LogWarning("Event name cannot be empty or whitespace.");
|
|
189
|
+
|
|
190
|
+
if (!eventNames.Add(eventQuality.eventName)) Debug.LogWarning($"Duplicate event name found: {eventQuality.eventName}. Event names must be unique.");
|
|
191
|
+
|
|
192
|
+
HashSet<string> paramNames = new HashSet<string>();
|
|
193
|
+
|
|
194
|
+
eventQuality.lstParam = eventQuality.lstParam.OrderBy(param => param.paramName).ToList();
|
|
195
|
+
|
|
196
|
+
foreach (var param in eventQuality.lstParam)
|
|
197
|
+
{
|
|
198
|
+
if (string.IsNullOrWhiteSpace(param.paramName)) Debug.LogWarning("Event parameter name cannot be empty or whitespace.");
|
|
199
|
+
|
|
200
|
+
if (!paramNames.Add(param.paramName)) Debug.LogWarning($"Duplicate parameter name found in event {eventQuality.eventName}: {param.paramName}. Parameter names must be unique within an event.");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
EditorUtility.SetDirty(this);
|
|
204
|
+
}
|
|
205
|
+
#endif
|
|
206
|
+
|
|
207
|
+
public void CheckEventQuality(string eventName, Dictionary<string, object> parameters)
|
|
208
|
+
{
|
|
209
|
+
if (!DictEvent.ContainsKey(eventName))
|
|
210
|
+
{
|
|
211
|
+
LogWarningOnce($"Event not found in AnalyticQuality: {eventName}");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
DictEvent[eventName].ValidateParams(eventName, parameters);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Linq;
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
|
|
5
|
+
namespace Amanotes.Core.Internal
|
|
6
|
+
{
|
|
7
|
+
[CreateAssetMenu(fileName = "AmaGDKEmbedRemoteConfig", menuName = "Ama GDK/Embed Remote Config", order = 2)]
|
|
8
|
+
[Serializable]
|
|
9
|
+
internal class EmbedRemoteConfigAsset : ScriptableObject
|
|
10
|
+
{
|
|
11
|
+
public int version = 1;
|
|
12
|
+
[SerializeField]
|
|
13
|
+
internal string activeId;
|
|
14
|
+
public bool overrideInEditor;
|
|
15
|
+
public KeyValue[] overrideConfig;
|
|
16
|
+
[SerializeField]
|
|
17
|
+
internal EmbedConfig[] configs;
|
|
18
|
+
private EmbedConfig activeConfig;
|
|
19
|
+
internal EmbedConfig ActiveConfig
|
|
20
|
+
{
|
|
21
|
+
get
|
|
22
|
+
{
|
|
23
|
+
if (activeConfig != null) return activeConfig;
|
|
24
|
+
activeConfig = GetConfigById(activeId);
|
|
25
|
+
return activeConfig;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
internal EmbedConfig GetConfigById(string id)
|
|
30
|
+
{
|
|
31
|
+
var config = configs?.FirstOrDefault(a => a.id == id);
|
|
32
|
+
if (config == null)
|
|
33
|
+
Logging.LogWarningOnce($"Not found embedded config <{id}>");
|
|
34
|
+
return config;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
internal void SetActiveConfig(string id)
|
|
38
|
+
{
|
|
39
|
+
activeId = id;
|
|
40
|
+
activeConfig = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[Serializable]
|
|
45
|
+
internal class EmbedConfig
|
|
46
|
+
{
|
|
47
|
+
public string id;
|
|
48
|
+
public string fetchUrl;
|
|
49
|
+
public KeyValue[] items;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
[Serializable]
|
|
53
|
+
internal class KeyValue
|
|
54
|
+
{
|
|
55
|
+
public string key;
|
|
56
|
+
public string value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|