Compare commits
1 Commits
main
...
feature/In
| Author | SHA1 | Date |
|---|---|---|
|
|
3f10cf718e |
|
|
@ -1,4 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
namespace Guru.Editor
|
namespace Guru.Editor
|
||||||
{
|
{
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
@ -11,34 +12,23 @@ namespace Guru.Editor
|
||||||
|
|
||||||
public class AppBuildParam
|
public class AppBuildParam
|
||||||
{
|
{
|
||||||
public const string TargetNameAndroid = "Android";
|
|
||||||
public const string TargetNameIOS = "iOS";
|
|
||||||
|
|
||||||
//------------ Basic ----------------
|
//------------ Basic ----------------
|
||||||
public bool IsBuildRelease = false; // 是否构建发布包体
|
public bool IsBuildRelease; // 是否构建发布包体
|
||||||
public bool IsBuildShowLog = false; // 是否显示日志
|
public bool IsBuildShowLog; // 是否显示日志
|
||||||
public AppBuilderType BuilderType; // 构建类型
|
public AppBuilderType BuilderType; // 构建类型
|
||||||
public string BuildVersion = ""; // 构建版本号, 填写后会依据此版本设置应用的 Version
|
public string BuildVersion = ""; // 构建版本号, 填写后会依据此版本设置应用的 Version
|
||||||
public bool AutoSetBuildNumber = true; // 自动设置构建号, 可参考 Guru的SDK 接入说明文档
|
public bool AutoSetBuildNumber = true; // 自动设置构建号, 可参考 Guru的SDK 接入说明文档
|
||||||
public bool UseGuruCerts = true; // 是否使用 Guru 的证书打包
|
public bool UseGuruCerts = true; // 是否使用 Guru 的证书打包
|
||||||
public string TargetName = "";
|
|
||||||
//------------ Android ----------------
|
//------------ Android ----------------
|
||||||
public bool IsBuildAAB; // 是否构建 AAB 包体 ( GooglePlay 发布专用 )
|
public bool IsBuildAAB; // 是否构建 AAB 包体 ( GooglePlay 发布专用 )
|
||||||
public bool IsBuildSymbols = false; // 是否需要构建 Symbols.zip 文件 ( GooglePlay 发布专用 )
|
public bool IsBuildSymbols = false; // 是否需要构建 Symbols.zip 文件 ( GooglePlay 发布专用 )
|
||||||
public int AndroidTargetVersion = 0; // Android SDK 版本设置 ( GooglePlay 发布专用 )
|
public int AndroidTargetVersion = 0; // Android SDK 版本设置 ( GooglePlay 发布专用 )
|
||||||
public bool AndroidUseMinify = false; // 是否开启 Android 的代码混淆和保护文件
|
public bool AndroidUseMinify = false; // 是否开启 Android 的代码混淆和保护文件
|
||||||
public bool DebugWithMono = true; // 是否使用 Mono 编译项目 ( Android Debug包专用 )
|
public bool DebugWithMono = true; // 是否使用 Mono 编译项目 ( Android Debug包专用 )
|
||||||
public string AndroidKeystorePath = ""; // Android KeyStore 文件名
|
|
||||||
public string AndroidKeystorePass = ""; // Android KeyStore 文件名
|
|
||||||
public string AndroidAlias = ""; // Android KeyStore 文件名
|
|
||||||
public string AndroidAliasPass = ""; // Android KeyStore 文件名
|
|
||||||
//------------ iOS ----------------
|
//------------ iOS ----------------
|
||||||
public string IOSTargetVersion = ""; // IOS SDK 版本设置 ( iOS 发布专用 )
|
public string IOSTargetVersion = ""; // IOS SDK 版本设置 ( iOS 发布专用 )
|
||||||
public string IOSTeamId = ""; // IOS 打包 TeamId ( iOS 使用专用的开发证书后开启 )
|
public string IOSTeamId = ""; // IOS 打包 TeamId ( iOS 使用专用的开发证书后开启 )
|
||||||
public string CompanyName = ""; // 发布厂商的名称
|
public string CompanyName = ""; // 发布厂商的名称
|
||||||
//------------ Publish -------------
|
|
||||||
public bool AutoPublish = false;
|
|
||||||
public string PgyerAPIKey = "";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -48,14 +38,13 @@ namespace Guru.Editor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static AppBuildParam Build(bool isRelease, AppBuilderType builderType = AppBuilderType.Editor, string version = "", bool autoBuildNumber = true, string companyName = "",
|
public static AppBuildParam Build(bool isRelease, AppBuilderType builderType = AppBuilderType.Editor, string version = "", bool autoBuildNumber = true, string companyName = "",
|
||||||
string targetName = "", bool buildShowLog = false, bool useGuruCerts = true,
|
bool buildShowLog = false, bool useGuruCerts = true,
|
||||||
bool buildSymbols = false, bool buildAAB = false, bool useMinify = false, int androidTargetVersion = 0, bool debugWithMono = true,
|
bool buildSymbols = false, bool buildAAB = false, bool useMinify = false, int androidTargetVersion = 0, bool debugWithMono = true,
|
||||||
string iOSTargetVersion = "", string iOSTeamId = "")
|
string iOSTargetVersion = "", string iOSTeamId = "")
|
||||||
{
|
{
|
||||||
return new AppBuildParam()
|
return new AppBuildParam()
|
||||||
{
|
{
|
||||||
TargetName = targetName,
|
|
||||||
IsBuildRelease = isRelease,
|
IsBuildRelease = isRelease,
|
||||||
IsBuildShowLog = buildShowLog,
|
IsBuildShowLog = buildShowLog,
|
||||||
BuilderType = builderType,
|
BuilderType = builderType,
|
||||||
|
|
@ -86,18 +75,14 @@ namespace Guru.Editor
|
||||||
/// <param name="useMinify"></param>
|
/// <param name="useMinify"></param>
|
||||||
/// <param name="androidTargetVersion"></param>
|
/// <param name="androidTargetVersion"></param>
|
||||||
/// <param name="debugWithMono"></param>
|
/// <param name="debugWithMono"></param>
|
||||||
/// <param name="isBuildAAB"></param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static AppBuildParam AndroidParam(bool isRelease, string version = "", bool autoBuildNumber = true,
|
public static AppBuildParam AndroidParam(bool isRelease, string version = "", bool autoBuildNumber = true, AppBuilderType builderType = AppBuilderType.Editor,
|
||||||
AppBuilderType builderType = AppBuilderType.Editor,
|
string companyName = "", bool useGuruCerts = true, bool useMinify = false, int androidTargetVersion = 0, bool debugWithMono = true)
|
||||||
string companyName = "", bool useGuruCerts = true, bool useMinify = false, int androidTargetVersion = 0,
|
|
||||||
bool debugWithMono = true, bool isBuildAAB = false)
|
|
||||||
{
|
{
|
||||||
bool buildAAB = isBuildAAB;
|
bool buildAAB = isRelease;
|
||||||
bool buildShowLog = isRelease;
|
bool buildShowLog = isRelease;
|
||||||
bool buildSymbols = isRelease;
|
bool buildSymbols = isRelease;
|
||||||
string targetName = TargetNameAndroid;
|
return Build(isRelease, builderType, version, autoBuildNumber,companyName, buildShowLog, useGuruCerts, buildSymbols, buildAAB, useMinify, androidTargetVersion, debugWithMono);
|
||||||
return Build(isRelease, builderType, version, autoBuildNumber,companyName, targetName, buildShowLog, useGuruCerts, buildSymbols, buildAAB, useMinify, androidTargetVersion, debugWithMono);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -117,8 +102,7 @@ namespace Guru.Editor
|
||||||
string companyName = "", bool useGuruCerts = true, string iOSTargetVersion = "", string iOSTeamId = "" )
|
string companyName = "", bool useGuruCerts = true, string iOSTargetVersion = "", string iOSTeamId = "" )
|
||||||
{
|
{
|
||||||
bool buildShowLog = isRelease;
|
bool buildShowLog = isRelease;
|
||||||
string targetName = TargetNameIOS;
|
return Build(isRelease, builderType, version, autoBuildNumber, companyName, buildShowLog, useGuruCerts, iOSTargetVersion:iOSTargetVersion, iOSTeamId:iOSTeamId);
|
||||||
return Build(isRelease, builderType, version, autoBuildNumber, companyName, targetName, buildShowLog, useGuruCerts, iOSTargetVersion:iOSTargetVersion, iOSTeamId:iOSTeamId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,68 +17,30 @@ namespace Guru.Editor
|
||||||
public static int AndroidTargetSdkVersion = 33;
|
public static int AndroidTargetSdkVersion = 33;
|
||||||
public static string IOSTargetOSVersion = "13.0";
|
public static string IOSTargetOSVersion = "13.0";
|
||||||
public static string GuruIOSTeamId = "39253T242A";
|
public static string GuruIOSTeamId = "39253T242A";
|
||||||
public static string GuruKeystoreName => "guru_key.jks";
|
public static string KeystoreName => $"guru_key.jks";
|
||||||
public static string GuruKeystorePass => "guru0622";
|
public static string KeystorePath => Application.dataPath + $"/Plugins/Android/{KeystoreName}";
|
||||||
public static string GuruAliasName => "guru";
|
public static string ProguardName => $"proguard-user.txt";
|
||||||
public static string GuruAliasPass => "guru0622";
|
|
||||||
public static string GuruKeystorePath => Application.dataPath + $"/Plugins/Android/{GuruKeystoreName}";
|
|
||||||
public static string ProguardName => "proguard-user.txt";
|
|
||||||
public static string ProguardPath => Application.dataPath + $"/Plugins/Android/{ProguardName}";
|
public static string ProguardPath => Application.dataPath + $"/Plugins/Android/{ProguardName}";
|
||||||
public static string OutputDirName => "BuildOutput";
|
|
||||||
|
|
||||||
#region 构建接口
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 直接调用 Build 接口
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buildParam"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string Build(AppBuildParam buildParam)
|
|
||||||
{
|
|
||||||
string outputPath = string.Empty;
|
|
||||||
switch (buildParam.TargetName)
|
|
||||||
{
|
|
||||||
case AppBuildParam.TargetNameAndroid:
|
|
||||||
SwitchBuildPlatform(BuildTarget.Android);
|
|
||||||
outputPath = BuildAndroid(buildParam);
|
|
||||||
break;
|
|
||||||
case AppBuildParam.TargetNameIOS:
|
|
||||||
SwitchBuildPlatform(BuildTarget.iOS);
|
|
||||||
outputPath = BuildIOS(buildParam);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Debug.Log($"<color=red> Unsupported build target: {buildParam.TargetName}. Skip build...</color>");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 构建 Android 接口
|
#region 构建 Android 接口
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建 Android 包体
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buildParam"></param>
|
/// <param name="buildParam"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string BuildAndroid(AppBuildParam buildParam)
|
public static string BuildAndroid(AppBuildParam buildParam)
|
||||||
{
|
{
|
||||||
// 切换平台
|
//切换平台
|
||||||
SwitchBuildPlatform(BuildTarget.Android);
|
BuildSwitchPlatform(BuildTarget.Android);
|
||||||
// 打包通用设置
|
//打包通用设置
|
||||||
ChangeBuildPlayerCommonSetting(buildParam, BuildTargetGroup.Android);
|
ChangeBuildPlayerCommonSetting(buildParam, BuildTargetGroup.Android);
|
||||||
|
|
||||||
var isDebug = !buildParam.IsBuildRelease;
|
var isDebug = !buildParam.IsBuildRelease;
|
||||||
var useMinify = buildParam.AndroidUseMinify;
|
var useMinify = buildParam.AndroidUseMinify;
|
||||||
var buildNumber= GetBuildNumberString(BuildTarget.Android);
|
var buildNumber= GetBuildNumberString(BuildTarget.Android);
|
||||||
if(buildParam.AutoSetBuildNumber) buildNumber = ChangeBuildNumber(BuildTarget.Android);
|
if(buildParam.AutoSetBuildNumber) buildNumber= ChangeBuildNumber(BuildTarget.Android);
|
||||||
|
|
||||||
// 保存版本信息
|
|
||||||
SaveBuildVersion(buildParam.BuildVersion, buildNumber);
|
|
||||||
|
|
||||||
//android专用打包设置
|
//android专用打包设置
|
||||||
EditorUserBuildSettings.buildAppBundle = buildParam.IsBuildAAB;
|
EditorUserBuildSettings.buildAppBundle = buildParam.IsBuildAAB;
|
||||||
EditorUserBuildSettings.development = isDebug;
|
EditorUserBuildSettings.development = isDebug;
|
||||||
|
|
@ -98,19 +60,10 @@ namespace Guru.Editor
|
||||||
{
|
{
|
||||||
// ---- 使用 Guru 专用的 KeyStore ----
|
// ---- 使用 Guru 专用的 KeyStore ----
|
||||||
PlayerSettings.Android.useCustomKeystore = true;
|
PlayerSettings.Android.useCustomKeystore = true;
|
||||||
PlayerSettings.Android.keystoreName = GuruKeystorePath;
|
PlayerSettings.Android.keystoreName = KeystorePath;
|
||||||
PlayerSettings.Android.keystorePass = GuruKeystorePass;
|
PlayerSettings.Android.keystorePass = "guru0622";
|
||||||
PlayerSettings.Android.keyaliasName = GuruAliasName;
|
PlayerSettings.Android.keyaliasName = "guru";
|
||||||
PlayerSettings.Android.keyaliasPass = GuruAliasPass;
|
PlayerSettings.Android.keyaliasPass = "guru0622";
|
||||||
}
|
|
||||||
else if(!string.IsNullOrEmpty(buildParam.AndroidKeystorePath))
|
|
||||||
{
|
|
||||||
// ---- 使用 Custom 的 KeyStore ----
|
|
||||||
PlayerSettings.Android.useCustomKeystore = true;
|
|
||||||
PlayerSettings.Android.keystoreName = buildParam.AndroidKeystorePath;
|
|
||||||
PlayerSettings.Android.keystorePass = buildParam.AndroidKeystorePass;
|
|
||||||
PlayerSettings.Android.keyaliasName = buildParam.AndroidAlias;
|
|
||||||
PlayerSettings.Android.keyaliasPass = buildParam.AndroidAliasPass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7 | AndroidArchitecture.ARM64; //只构建 armv7 和 arm64
|
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7 | AndroidArchitecture.ARM64; //只构建 armv7 和 arm64
|
||||||
|
|
@ -124,7 +77,7 @@ namespace Guru.Editor
|
||||||
string version = Application.version;
|
string version = Application.version;
|
||||||
string extension = buildParam.IsBuildAAB ? ".aab" : ".apk";
|
string extension = buildParam.IsBuildAAB ? ".aab" : ".apk";
|
||||||
if (EditorUserBuildSettings.exportAsGoogleAndroidProject) extension = ""; // 输出工程
|
if (EditorUserBuildSettings.exportAsGoogleAndroidProject) extension = ""; // 输出工程
|
||||||
string outputDir = Path.GetFullPath($"{Application.dataPath }/../{OutputDirName}/Android");
|
string outputDir = Path.GetFullPath($"{Application.dataPath }/../BuildOutput/Android");
|
||||||
apkPath = $"{outputDir}/{Application.productName.Replace(" ","_")}_{symbolDefine}_{version}_{buildNumber}{extension}";
|
apkPath = $"{outputDir}/{Application.productName.Replace(" ","_")}_{symbolDefine}_{version}_{buildNumber}{extension}";
|
||||||
if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir);
|
if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir);
|
||||||
|
|
||||||
|
|
@ -134,11 +87,7 @@ namespace Guru.Editor
|
||||||
{
|
{
|
||||||
Open(outputDir);
|
Open(outputDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildParam.AutoPublish)
|
|
||||||
{
|
|
||||||
GuruPublishHelper.Publish(apkPath, buildParam.PgyerAPIKey); // 直接发布版本
|
|
||||||
}
|
|
||||||
return apkPath;
|
return apkPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,8 +97,8 @@ namespace Guru.Editor
|
||||||
private static bool DeployAndroidKeystore()
|
private static bool DeployAndroidKeystore()
|
||||||
{
|
{
|
||||||
var dir = GetWorkingDir();
|
var dir = GetWorkingDir();
|
||||||
var from = $"{dir}/{GuruKeystoreName}";
|
var from = $"{dir}/{KeystoreName}";
|
||||||
var to = GuruKeystorePath;
|
var to = KeystorePath;
|
||||||
|
|
||||||
if (File.Exists(to)) return true;
|
if (File.Exists(to)) return true;
|
||||||
|
|
||||||
|
|
@ -187,19 +136,15 @@ namespace Guru.Editor
|
||||||
|
|
||||||
#region 构建 IOS 接口
|
#region 构建 IOS 接口
|
||||||
|
|
||||||
public static string BuildIOS(AppBuildParam buildParam)
|
public static void BuildIOS(AppBuildParam buildParam)
|
||||||
{
|
{
|
||||||
//切换平台
|
//切换平台
|
||||||
SwitchBuildPlatform(BuildTarget.iOS);
|
BuildSwitchPlatform(BuildTarget.iOS);
|
||||||
//打包通用设置
|
//打包通用设置
|
||||||
ChangeBuildPlayerCommonSetting(buildParam, BuildTargetGroup.iOS);
|
ChangeBuildPlayerCommonSetting(buildParam, BuildTargetGroup.iOS);
|
||||||
|
|
||||||
//修改打包版本号
|
//修改打包版本号
|
||||||
var buildNumber= GetBuildNumberString(BuildTarget.Android);
|
if(buildParam.AutoSetBuildNumber) ChangeBuildNumber(BuildTarget.iOS);
|
||||||
if(buildParam.AutoSetBuildNumber) buildNumber = ChangeBuildNumber(BuildTarget.iOS);
|
|
||||||
|
|
||||||
// 保存版本信息
|
|
||||||
SaveBuildVersion(buildParam.BuildVersion, buildNumber);
|
|
||||||
|
|
||||||
var isDebug = !buildParam.IsBuildRelease;
|
var isDebug = !buildParam.IsBuildRelease;
|
||||||
|
|
||||||
|
|
@ -224,28 +169,26 @@ namespace Guru.Editor
|
||||||
}
|
}
|
||||||
|
|
||||||
//打包
|
//打包
|
||||||
string outputDir = Path.GetFullPath($"{Application.dataPath }/../{OutputDirName}/Xcode");
|
string xcodePath = Application.dataPath + "/../../xcode";
|
||||||
if (Directory.Exists(outputDir))
|
if (Directory.Exists(xcodePath))
|
||||||
{
|
{
|
||||||
Directory.Delete(outputDir, true);
|
Directory.Delete(xcodePath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建后打开路径
|
// 构建后打开路径
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BuildOptions opts = isDebug ? BuildOptions.Development : BuildOptions.None;
|
BuildOptions opts = isDebug ? BuildOptions.Development : BuildOptions.None;
|
||||||
BuildPipeline.BuildPlayer(GetBuildScenes(), outputDir, BuildTarget.iOS, BuildOptions.None);
|
BuildPipeline.BuildPlayer(GetBuildScenes(), xcodePath, BuildTarget.iOS, BuildOptions.None);
|
||||||
if (buildParam.BuilderType == AppBuilderType.Editor)
|
if (buildParam.BuilderType == AppBuilderType.Editor)
|
||||||
{
|
{
|
||||||
Open(outputDir);
|
Open(xcodePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogError(e.Message);
|
Debug.LogError(e.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputDir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -278,7 +221,7 @@ namespace Guru.Editor
|
||||||
/// 平台切换
|
/// 平台切换
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="targetPlatform"></param>
|
/// <param name="targetPlatform"></param>
|
||||||
private static void SwitchBuildPlatform(BuildTarget targetPlatform)
|
private static void BuildSwitchPlatform(BuildTarget targetPlatform)
|
||||||
{
|
{
|
||||||
if (EditorUserBuildSettings.activeBuildTarget != targetPlatform)
|
if (EditorUserBuildSettings.activeBuildTarget != targetPlatform)
|
||||||
{
|
{
|
||||||
|
|
@ -375,12 +318,6 @@ namespace Guru.Editor
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void SaveBuildVersion(string version, string code)
|
|
||||||
{
|
|
||||||
GuruAppVersion.SaveToDisk(version, code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取打包场景
|
/// 获取打包场景
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,338 +0,0 @@
|
||||||
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using System.IO;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
public class PgyerAPI
|
|
||||||
{
|
|
||||||
public const string Version = "1.0.0";
|
|
||||||
|
|
||||||
internal static readonly string DefaultBashPathWin = "C:\\Program Files\\Git\\bin\\bash.exe";
|
|
||||||
internal static readonly string DefaultBashPathMac = "/bin/bash";
|
|
||||||
internal static readonly string ShellFile = "pgyer_upload.sh";
|
|
||||||
private static readonly string GuruAPIKey = "20a3d1106b802abbd84ec687eedf17eb";
|
|
||||||
private static readonly string PgyerHost = "https://www.pgyer.com";
|
|
||||||
internal static string WorkingDir => $"{Application.dataPath.Replace("Assets", "Library")}/guru_publish";
|
|
||||||
|
|
||||||
public static string GetDownloadUrl(string shortUrl) => $"{PgyerHost}/{shortUrl}";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发布产品到蒲公英平台
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="packagePath"></param>
|
|
||||||
/// <param name="apiKey"></param>
|
|
||||||
/// <param name="bashPath"></param>
|
|
||||||
/// <param name="callback"></param>
|
|
||||||
public static void PublishToPgyer(string packagePath, string apiKey = "", string bashPath = "",
|
|
||||||
Action<string> callback = null)
|
|
||||||
{
|
|
||||||
if (File.Exists(packagePath))
|
|
||||||
{
|
|
||||||
Debug.Log($"=== START PUBLISH APP: {packagePath}");
|
|
||||||
CheckWorkingDir();
|
|
||||||
CallPublishShell(packagePath, apiKey, bashPath, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CheckWorkingDir()
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(WorkingDir))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(WorkingDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = $"{WorkingDir}/{ShellFile}";
|
|
||||||
if (!File.Exists(file))
|
|
||||||
{
|
|
||||||
var from = GetShellPath();
|
|
||||||
if (File.Exists(from))
|
|
||||||
{
|
|
||||||
File.Copy(from, file);
|
|
||||||
#if UNITY_EDITOR_OSX
|
|
||||||
RunCmd("chmod", $"+x {file}", workpath: WorkingDir);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError($"[Publisher] Source shell file not found :{from}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取 CMD 命令路径
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static string GetShellPath()
|
|
||||||
{
|
|
||||||
var path = "";
|
|
||||||
var guids = AssetDatabase.FindAssets($"{nameof(PgyerAPI)} t:script");
|
|
||||||
if (guids.Length > 0)
|
|
||||||
{
|
|
||||||
path = Path.Combine(Directory.GetParent(AssetDatabase.GUIDToAssetPath(guids[0])).FullName, ShellFile);
|
|
||||||
return Path.GetFullPath(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
path = Path.GetFullPath(
|
|
||||||
$"{Application.dataPath.Replace("Assets", "Packages")}/com.guru.unity.sdk.core/Editor/BuildTool/{ShellFile}");
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static void RunCmd(string cmd, string args, Action<string> callback = null, string workpath = "")
|
|
||||||
{
|
|
||||||
System.Diagnostics.Process process = new System.Diagnostics.Process();
|
|
||||||
process.StartInfo.FileName = cmd;
|
|
||||||
process.StartInfo.Arguments = args;
|
|
||||||
process.StartInfo.UseShellExecute = false;
|
|
||||||
process.StartInfo.RedirectStandardOutput = true;
|
|
||||||
process.StartInfo.RedirectStandardError = true;
|
|
||||||
process.StartInfo.CreateNoWindow = true;
|
|
||||||
if (!string.IsNullOrEmpty(workpath)) process.StartInfo.WorkingDirectory = workpath;
|
|
||||||
process.Start();
|
|
||||||
string log = process.StandardOutput.ReadToEnd();
|
|
||||||
callback?.Invoke(log);
|
|
||||||
process.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 在 mac 下进行发布
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="packagePath"></param>
|
|
||||||
/// <param name="apiKey"></param>
|
|
||||||
/// <param name="bashPath"></param>
|
|
||||||
/// <param name="callback"></param>
|
|
||||||
private static void CallPublishShell(string packagePath, string apiKey = "", string bashPath = "",
|
|
||||||
Action<string> callback = null)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(bashPath))
|
|
||||||
{
|
|
||||||
#if UNITY_EDITOR_OSX
|
|
||||||
bashPath = DefaultBashPathMac;
|
|
||||||
#elif UNITY_EDITOR_WIN
|
|
||||||
bashPath = DefaultBashPathWin.Replace("\\", "/");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(bashPath))
|
|
||||||
{
|
|
||||||
string msg = $"Error: Bash file not found at path: {bashPath}! skip publishing!";
|
|
||||||
Debug.LogError(msg);
|
|
||||||
callback?.Invoke(msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
packagePath = packagePath.Replace("\\", "/");
|
|
||||||
if (string.IsNullOrEmpty(apiKey)) apiKey = GuruAPIKey;
|
|
||||||
var args = $"-c \"./{ShellFile} -k {apiKey} {packagePath}\"";
|
|
||||||
// Debug.Log(bashPath);
|
|
||||||
// Debug.Log(args);
|
|
||||||
// Debug.Log(WorkingDir);
|
|
||||||
RunCmd(bashPath, args, callback, WorkingDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Guru 包体上传工具
|
|
||||||
/// </summary>
|
|
||||||
public class GuruPublishHelper
|
|
||||||
{
|
|
||||||
// Check Env and Exe files
|
|
||||||
private static string EvnCheck()
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(PgyerAPI.WorkingDir))
|
|
||||||
Directory.CreateDirectory(PgyerAPI.WorkingDir);
|
|
||||||
|
|
||||||
// #1 --- read from cached file with available path
|
|
||||||
string bash_path = "";
|
|
||||||
var envFile = $"{PgyerAPI.WorkingDir}/.env";
|
|
||||||
if (File.Exists(envFile))
|
|
||||||
{
|
|
||||||
bash_path = File.ReadAllText(envFile);
|
|
||||||
return bash_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #2 --- Try to find bash exe file from default path
|
|
||||||
bash_path = PgyerAPI.DefaultBashPathMac;
|
|
||||||
#if UNITY_EDITOR_WIN
|
|
||||||
bash_path = PgyerAPI.DefaultBashPathWin;
|
|
||||||
#endif
|
|
||||||
if (File.Exists(bash_path))
|
|
||||||
{
|
|
||||||
bash_path = bash_path.Replace("\\", "/");
|
|
||||||
File.WriteAllText(envFile, bash_path);
|
|
||||||
return bash_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #3 --- Try to let user select bash exe file from disk
|
|
||||||
string title = "选择 bash 可执行文件";
|
|
||||||
string despath = "/bin";
|
|
||||||
string exts = "*";
|
|
||||||
|
|
||||||
#if UNITY_EDITOR_WIN
|
|
||||||
despath = "C:\\Program Files\\";
|
|
||||||
title = $"选择 bash 可执行文件, 例如: {despath}\\Git\\bin\\bash.exe";
|
|
||||||
exts = "exe";
|
|
||||||
#endif
|
|
||||||
bash_path = EditorUtility.OpenFilePanel(title, despath, exts);
|
|
||||||
if (File.Exists(bash_path))
|
|
||||||
{
|
|
||||||
File.WriteAllText(envFile, bash_path.Replace("\\", "/"));
|
|
||||||
}
|
|
||||||
return bash_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[MenuItem("Guru/Publish/Android APK...")]
|
|
||||||
private static void EditorPublishAPK()
|
|
||||||
{
|
|
||||||
SelectAndPublish();
|
|
||||||
}
|
|
||||||
|
|
||||||
// [MenuItem("Guru/Publish/Publish Release AAB...")]
|
|
||||||
// private static void EditorPublishAAB()
|
|
||||||
// {
|
|
||||||
// SelectAndPublish("aab");
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 直接发布版本
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="appPath"></param>
|
|
||||||
/// <param name="apiKey"></param>
|
|
||||||
public static void Publish(string appPath, string apiKey = "")
|
|
||||||
{
|
|
||||||
string bash_path = EvnCheck();
|
|
||||||
if (string.IsNullOrEmpty(bash_path))
|
|
||||||
{
|
|
||||||
ShowDialog("找不到 Bash 执行文件", $"Bash文件不存在: {bash_path}!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!File.Exists(appPath))
|
|
||||||
{
|
|
||||||
ShowDialog("找不到包体文件", $"包体文件不存在: {appPath}!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PgyerAPI.PublishToPgyer(appPath, apiKey, bash_path, OnResponse);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 选择文件及发布版本
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="extension"></param>
|
|
||||||
/// <param name="apiKey"></param>
|
|
||||||
public static void SelectAndPublish(string extension = "apk", string apiKey = "")
|
|
||||||
{
|
|
||||||
string file = EditorUtility.OpenFilePanel("选择包体", $"~/Downloads", extension);
|
|
||||||
Publish(file, apiKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Show system dialogs
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title"></param>
|
|
||||||
/// <param name="body"></param>
|
|
||||||
/// <param name="callback"></param>
|
|
||||||
/// <param name="cancelAction"></param>
|
|
||||||
/// <param name="okName"></param>
|
|
||||||
/// <param name="cancelName"></param>
|
|
||||||
private static void ShowDialog(string title, string body, Action callback = null, Action cancelAction = null, string okName= "OK", string cancelName = "")
|
|
||||||
{
|
|
||||||
if (EditorUtility.DisplayDialog(title, body, okName, cancelName))
|
|
||||||
{
|
|
||||||
callback?.Invoke();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cancelAction?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// On pgyer response callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="log"></param>
|
|
||||||
private static void OnResponse(string log)
|
|
||||||
{
|
|
||||||
var logPath = $"{PgyerAPI.WorkingDir}/log.txt";
|
|
||||||
File.WriteAllText(logPath, log);
|
|
||||||
|
|
||||||
bool success = log.Contains(ResponseObject.HeadTag);
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
var json = log.Substring(log.IndexOf(ResponseObject.HeadTag, StringComparison.Ordinal));
|
|
||||||
var res = ResponseObject.Parse(json);
|
|
||||||
if (res != null)
|
|
||||||
{
|
|
||||||
var url = PgyerAPI.GetDownloadUrl(res.ShortUrl());
|
|
||||||
ShowDialog($"==== 上传成功 ({PgyerAPI.Version}) ====", $"包体 {res.BuildVersion()} ({res.BuildVersionCode()}) 上传成功!\n下载链接:{url}", () =>
|
|
||||||
{
|
|
||||||
Debug.Log($"INSTALL URL:{url}"); // output url
|
|
||||||
Application.OpenURL(url);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowDialog($"==== 上传失败 ({PgyerAPI.Version}) ====", $"上传文件失败, 查看详细日志: \n{logPath}", () =>
|
|
||||||
{
|
|
||||||
#if UNITY_EDITOR_OSX
|
|
||||||
EditorUtility.RevealInFinder(PgyerAPI.WorkingDir);
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
Application.OpenURL(PgyerAPI.WorkingDir);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
internal class ResponseObject
|
|
||||||
{
|
|
||||||
public int code;
|
|
||||||
public string message;
|
|
||||||
public PublishData data;
|
|
||||||
|
|
||||||
public static readonly string HeadTag = "{\"code\":";
|
|
||||||
|
|
||||||
public string BuildVersion() => data?.buildVersion ?? "0.0.0";
|
|
||||||
public string BuildVersionCode() => data?.buildVersionNo ?? "0";
|
|
||||||
public string ShortUrl() => data?.buildShortcutUrl ?? "#";
|
|
||||||
|
|
||||||
|
|
||||||
public static bool IsValid(string json)
|
|
||||||
{
|
|
||||||
return json.Contains(HeadTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ResponseObject Parse(string json)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(json)) return null;
|
|
||||||
if (!IsValid(json)) return null;
|
|
||||||
return JsonUtility.FromJson<ResponseObject>(json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
internal class PublishData
|
|
||||||
{
|
|
||||||
public string buildIdentifier;
|
|
||||||
public string buildQRCodeURL;
|
|
||||||
public string buildShortcutUrl;
|
|
||||||
public string buildName;
|
|
||||||
public string buildVersion;
|
|
||||||
public string buildVersionNo;
|
|
||||||
public string buildUpdated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 615e8fb3101146048570e17d27bfbb9b
|
|
||||||
timeCreated: 1709605527
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
public class JenkinsHelper
|
|
||||||
{
|
|
||||||
public const string DefaultArgsTag = "+args";
|
|
||||||
|
|
||||||
public static AppBuildParam ParseJenkinsBuildParam(string[] commandlineArgs, string argsTag = "")
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(argsTag)) argsTag = DefaultArgsTag;
|
|
||||||
int len = commandlineArgs.Length;
|
|
||||||
|
|
||||||
Debug.Log($"------------ Jenkins set commands: {len} ------------");
|
|
||||||
|
|
||||||
var buildParam = new AppBuildParam()
|
|
||||||
{
|
|
||||||
BuilderType = AppBuilderType.Jenkins,
|
|
||||||
TargetName = "Android",
|
|
||||||
IsBuildAAB = false,
|
|
||||||
IsBuildSymbols = false,
|
|
||||||
AutoPublish = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
string p = "";
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
p = commandlineArgs[i];
|
|
||||||
// Debug.Log($"--- [{i}]: {p}");
|
|
||||||
|
|
||||||
if (p.StartsWith(argsTag))
|
|
||||||
{
|
|
||||||
// Debug.Log($"--- find param: {p}");
|
|
||||||
var args = p.Split('-').ToList();
|
|
||||||
if (args.Count > 1)
|
|
||||||
{
|
|
||||||
// Debug.Log($"--- ENV: {args[1]}");
|
|
||||||
if (args[1].ToUpper() == "RELEASE")
|
|
||||||
{
|
|
||||||
buildParam.IsBuildRelease = true;
|
|
||||||
buildParam.IsBuildShowLog = false;
|
|
||||||
buildParam.IsBuildSymbols = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buildParam.IsBuildRelease = false;
|
|
||||||
buildParam.IsBuildShowLog = true;
|
|
||||||
buildParam.IsBuildSymbols = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (args.Count > 2)
|
|
||||||
{
|
|
||||||
// Debug.Log($"--- VERSION: {args[2]}");
|
|
||||||
buildParam.BuildVersion = args[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取构建参数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="argsTag"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static AppBuildParam GetBuildParams(string argsTag = "")
|
|
||||||
{
|
|
||||||
return ParseJenkinsBuildParam(Environment.GetCommandLineArgs(), argsTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 6f2cde4b226e401babf1c1e9088e1673
|
|
||||||
timeCreated: 1711889309
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# 通过shell脚本来实现将本地app文件通过API上传到蒲公英
|
|
||||||
# https://www.pgyer.com/doc/view/api#fastUploadApp
|
|
||||||
#
|
|
||||||
|
|
||||||
# Display log. 1=enable, 0=disable
|
|
||||||
LOG_ENABLE=1
|
|
||||||
|
|
||||||
printHelp() {
|
|
||||||
echo "Usage: $0 -k <api_key> [OPTION]... file"
|
|
||||||
echo "Upload iOS or Android app package file to PGYER."
|
|
||||||
echo "Example: $0 -k xxxxxxxxxxxxxxx /data/app.ipa"
|
|
||||||
echo ""
|
|
||||||
echo "Description:"
|
|
||||||
echo " -k api_key (required) api key from PGYER"
|
|
||||||
echo " -t buildInstallType build install type, 1=public, 2=password, 3=invite"
|
|
||||||
echo " -p buildPassword build password, required if buildInstallType=2"
|
|
||||||
echo " -d buildUpdateDescription build update description"
|
|
||||||
echo " -e buildInstallDate build install date, 1=buildInstallStartDate~buildInstallEndDate, 2=forever"
|
|
||||||
echo " -s buildInstallStartDate build install start date, format: yyyy-MM-dd"
|
|
||||||
echo " -e buildInstallEndDate build install end date, format: yyyy-MM-dd"
|
|
||||||
echo " -c buildChannelShortcut build channel shortcut"
|
|
||||||
echo " -h help show this help"
|
|
||||||
echo ""
|
|
||||||
echo "Report bugs to: <https://github.com/PGYER/pgyer_api_example/issues>"
|
|
||||||
echo "Project home page: <https://github.com/PGYER/pgyer_api_example>"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
while getopts 'k:t:p:d:s:e:c:h' OPT; do
|
|
||||||
case $OPT in
|
|
||||||
k) api_key="$OPTARG";;
|
|
||||||
t) buildInstallType="$OPTARG";;
|
|
||||||
p) buildPassword="$OPTARG";;
|
|
||||||
d) buildUpdateDescription="$OPTARG";;
|
|
||||||
e) buildInstallDate="$OPTARG";;
|
|
||||||
s) buildInstallStartDate="$OPTARG";;
|
|
||||||
e) buildInstallEndDate="$OPTARG";;
|
|
||||||
c) buildChannelShortcut="$OPTARG";;
|
|
||||||
?) printHelp;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
shift $(($OPTIND - 1))
|
|
||||||
readonly file=$1
|
|
||||||
|
|
||||||
# check api_key exists
|
|
||||||
if [ -z "$api_key" ]; then
|
|
||||||
echo "api_key is empty"
|
|
||||||
printHelp
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check file exists
|
|
||||||
if [ ! -f "$file" ]; then
|
|
||||||
echo "file not exists"
|
|
||||||
printHelp
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check ext supported
|
|
||||||
buildType=${file##*.}
|
|
||||||
if [ "$buildType" != "ipa" ] && [ "$buildType" != "apk" ]; then
|
|
||||||
echo "file ext is not supported"
|
|
||||||
printHelp
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# functions
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
|
|
||||||
log() {
|
|
||||||
[ $LOG_ENABLE -eq 1 ] && echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
logTitle() {
|
|
||||||
log "-------------------------------- $* --------------------------------"
|
|
||||||
}
|
|
||||||
|
|
||||||
execCommand() {
|
|
||||||
log "$@"
|
|
||||||
result=$(eval $@)
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 获取上传凭证
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
|
|
||||||
logTitle "获取凭证"
|
|
||||||
|
|
||||||
command="curl -s"
|
|
||||||
[ -n "$api_key" ] && command="${command} --form-string '_api_key=${api_key}'";
|
|
||||||
[ -n "$buildType" ] && command="${command} --form-string 'buildType=${buildType}'";
|
|
||||||
[ -n "$buildInstallType" ] && command="${command} --form-string 'buildInstallType=${buildInstallType}'";
|
|
||||||
[ -n "$buildPassword" ] && command="${command} --form-string 'buildPassword=${buildPassword}'";
|
|
||||||
[ -n "$buildUpdateDescription" ] && command="${command} --form-string $'buildUpdateDescription=${buildUpdateDescription}'";
|
|
||||||
[ -n "$buildInstallDate" ] && command="${command} --form-string 'buildInstallDate=${buildInstallDate}'";
|
|
||||||
[ -n "$buildInstallStartDate" ] && command="${command} --form-string 'buildInstallStartDate=${buildInstallStartDate}'";
|
|
||||||
[ -n "$buildInstallEndDate" ] && command="${command} --form-string 'buildInstallEndDate=${buildInstallEndDate}'";
|
|
||||||
[ -n "$buildChannelShortcut" ] && command="${command} --form-string 'buildChannelShortcut=${buildChannelShortcut}'";
|
|
||||||
command="${command} http://www.pgyer.com/apiv2/app/getCOSToken";
|
|
||||||
execCommand $command
|
|
||||||
|
|
||||||
[[ "${result}" =~ \"endpoint\":\"([\:\_\.\/\\A-Za-z0-9\-]+)\" ]] && endpoint=`echo ${BASH_REMATCH[1]} | sed 's!\\\/!/!g'`
|
|
||||||
[[ "${result}" =~ \"key\":\"([\.a-z0-9]+)\" ]] && key=`echo ${BASH_REMATCH[1]}`
|
|
||||||
[[ "${result}" =~ \"signature\":\"([\=\&\_\;A-Za-z0-9\-]+)\" ]] && signature=`echo ${BASH_REMATCH[1]}`
|
|
||||||
[[ "${result}" =~ \"x-cos-security-token\":\"([\_A-Za-z0-9\-]+)\" ]] && x_cos_security_token=`echo ${BASH_REMATCH[1]}`
|
|
||||||
|
|
||||||
if [ -z "$key" ] || [ -z "$signature" ] || [ -z "$x_cos_security_token" ] || [ -z "$endpoint" ]; then
|
|
||||||
log "get upload token failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 上传文件
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
|
|
||||||
logTitle "上传文件"
|
|
||||||
|
|
||||||
file_name=${file##*/}
|
|
||||||
|
|
||||||
execCommand "curl -s -o /dev/null -w '%{http_code}' \
|
|
||||||
--form-string 'key=${key}' \
|
|
||||||
--form-string 'signature=${signature}' \
|
|
||||||
--form-string 'x-cos-security-token=${x_cos_security_token}' \
|
|
||||||
--form-string 'x-cos-meta-file-name=${file_name}' \
|
|
||||||
-F 'file=@${file}' ${endpoint}"
|
|
||||||
if [ $result -ne 204 ]; then # if http code != 204, upload failed
|
|
||||||
log "Upload failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 检查结果
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
|
|
||||||
logTitle "检查结果"
|
|
||||||
|
|
||||||
for i in {1..60}; do
|
|
||||||
execCommand "curl -s http://www.pgyer.com/apiv2/app/buildInfo?_api_key=${api_key}\&buildKey=${key}"
|
|
||||||
[[ "${result}" =~ \"code\":([0-9]+) ]] && code=`echo ${BASH_REMATCH[1]}`
|
|
||||||
if [ $code -eq 0 ]; then
|
|
||||||
echo $result
|
|
||||||
break
|
|
||||||
else
|
|
||||||
sleep 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
#proguard-adjust.pro
|
#proguard-adjust.pro
|
||||||
-keep public class com.adjust.sdk.** { *; }
|
-keep public class com.adjust.sdk.** { *; }
|
||||||
-keep class com.amazon.device.ads.** { *; }
|
-keep class com.amazon.device.ads.** { *; }
|
||||||
-keep class com.amazon.aps.** { *; }
|
|
||||||
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
|
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
|
||||||
com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
|
com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +97,3 @@
|
||||||
|
|
||||||
-keep class com.onevcat.uniwebview.* { *; }
|
-keep class com.onevcat.uniwebview.* { *; }
|
||||||
-keep class com.iab.omid.* { *; }
|
-keep class com.iab.omid.* { *; }
|
||||||
|
|
||||||
-keep public class com.tradplus.** { *; }
|
|
||||||
-keep class com.tradplus.ads.** { *; }
|
|
||||||
-keep class com.applovin.mediation.adapters.** { *; }
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "Guru.Editor",
|
"name": "Guru.Editor",
|
||||||
|
"rootNamespace": "",
|
||||||
"references": [
|
"references": [
|
||||||
"Guru.Runtime",
|
"Guru.Runtime"
|
||||||
"Guru.Notification"
|
|
||||||
],
|
],
|
||||||
"includePlatforms": [
|
"includePlatforms": [
|
||||||
"Editor"
|
"Editor"
|
||||||
|
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
#if UNITY_IOS
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
using UnityEditor.iOS.Xcode;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SKAdNetwork 注入逻辑
|
|
||||||
/// </summary>
|
|
||||||
public static class IOSPostBuild_SKAdNetwork
|
|
||||||
{
|
|
||||||
private static List<string> NETWORK_IDENTIFIER_ARRAY = new List<string>();
|
|
||||||
public static readonly string SKADNetworkIdentifier = "SKAdNetworkIdentifier";
|
|
||||||
|
|
||||||
private static readonly char DIR_CHAR = Path.DirectorySeparatorChar;
|
|
||||||
public static readonly string OS_PLATFORM_LOCATION = $"Assets/Guru/GuruBuildTool/Editor/IOS_POST_AD/";
|
|
||||||
public static readonly string SKADNetworkName = "SKADNetwork.plist";
|
|
||||||
|
|
||||||
[PostProcessBuild(10)]
|
|
||||||
private static void OnPostProcessBuild(BuildTarget buildTarget, string path)
|
|
||||||
{
|
|
||||||
if (buildTarget != BuildTarget.iOS)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var plistPath = Path.Combine(path, "Info.plist");
|
|
||||||
var plist = new PlistDocument();
|
|
||||||
plist.ReadFromFile(plistPath);
|
|
||||||
|
|
||||||
//设置SKAdNetworkItems
|
|
||||||
ReadSKADNetworkPlistFile();
|
|
||||||
var plistElementArray = plist.root.CreateArray("SKAdNetworkItems");
|
|
||||||
AddPlatformADNetworkIdentifier(plistElementArray, NETWORK_IDENTIFIER_ARRAY);
|
|
||||||
plist.WriteToFile(plistPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ReadSKADNetworkPlistFile()
|
|
||||||
{
|
|
||||||
string plistPath = Path.Combine(GetToolRootDir(), SKADNetworkName);
|
|
||||||
if (File.Exists(plistPath))
|
|
||||||
{
|
|
||||||
var plist = new PlistDocument();
|
|
||||||
plist.ReadFromFile(plistPath);
|
|
||||||
var skADNetworksArr = plist.root["SKAdNetworkItems"].AsArray();
|
|
||||||
if (skADNetworksArr != null)
|
|
||||||
{
|
|
||||||
foreach (var plistElement in skADNetworksArr.values)
|
|
||||||
{
|
|
||||||
var adNetworkValue = plistElement.AsDict()[SKADNetworkIdentifier].AsString();
|
|
||||||
if(!NETWORK_IDENTIFIER_ARRAY.Contains(adNetworkValue))
|
|
||||||
NETWORK_IDENTIFIER_ARRAY.Add(adNetworkValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Log($"[POST] --- Inject SKADNetwork Failed: {plistPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddPlatformADNetworkIdentifier(PlistElementArray plistElementArray, List<string> arrays)
|
|
||||||
{
|
|
||||||
foreach (var value in arrays)
|
|
||||||
{
|
|
||||||
PlistArrayAddDict(plistElementArray, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PlistArrayAddDict(PlistElementArray plistElementArray, string value)
|
|
||||||
{
|
|
||||||
plistElementArray.AddDict().SetString(SKADNetworkIdentifier, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static string GetToolRootDir()
|
|
||||||
{
|
|
||||||
var guids = AssetDatabase.FindAssets($"{nameof(IOSPostBuild_SKAdNetwork)}");
|
|
||||||
if (guids.Length > 0)
|
|
||||||
{
|
|
||||||
var path = Directory.GetParent(AssetDatabase.GUIDToAssetPath(guids[0])).FullName;
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
return $"{Application.dataPath.Replace("Assets", "Packages")}/com.guru.unity.sdk.core/Editor/GuruBuildSuit/IOS_POST_AD";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: be1ddc59e4ac2416f8174a5611f0c98a
|
|
||||||
timeCreated: 1632375624
|
|
||||||
|
|
@ -1,925 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkItems</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>cstr6suwn9.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4fzdc2evr5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4pfyvq9l8r.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>2fnua5tdw4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ydx93a7ass.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>5a6flpkh64.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>p78axxw29g.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>v72qych5uu.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ludvb6z3bs.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>cp8zw746q7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3sh42y64q3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>c6k4g5qg8m.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>s39g8k73mm.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3qy4746246.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>f38h382jlk.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>hs6bdukanm.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>v4nxqhlyqp.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>wzmmz9fp6w.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>yclnxrl5pm.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>t38b2kh725.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>7ug5zh24hu.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>gta9lk7p23.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>vutu7akeur.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>y5ghdn5j9k.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>n6fk4nfna4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>v9wttpbfk9.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>n38lu8286q.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>47vhws6wlr.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>kbd757ywx3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9t245vhmpl.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>eh6m2bh4zr.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>a2p9lx4jpn.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>22mmun2rn5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4468km3ulz.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>2u9pt9hc89.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>8s468mfl3y.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>klf5c3l5u5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ppxm28t8ap.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ecpz2srf59.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>uw77j35x4d.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>pwa73g5rt2.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>mlmmfzh3r3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>578prtvx9j.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4dzt52r2t5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>e5fvkxwrpn.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>8c4e2ghe7u.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>zq492l623r.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3rd42ekr43.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3qcr597p9d.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>238da6jt44.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>24t9a8vw3c.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>24zw6aqk47.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>252b5q8x7y.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>275upjj5gd.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>294l99pt4k.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>32z4fx6l9h.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3l6bd9hu43.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>424m5254lk.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>44jx6755aq.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>44n7hlldy6.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>488r3q3dtq.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4mn522wn87.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>4w7y6s5ca2.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>523jb4fst2.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>52fl2v3hgk.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>54nzkqm89y.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>5l3tpt7t6e.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>5lm9lj6jb7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>5tjdwbrq8w.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>6964rsfnh4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>6g9af3uyq4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>6p4ks3rnbw.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>6v7lgmsu45.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>6xzpu9s2p8.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>737z793b9f.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>74b6s63p6l.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>79pbpufp6p.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>7fmhfwg9en.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>7rz58n8ntl.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>84993kbrcf.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>89z7zv988g.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>8m87ys6875.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>8r8llnkz5a.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>97r2b46745.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9b89h5y424.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9nlqeag3gk.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9rd848q2bz.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9vvzujtq5s.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9yg77x724h.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>a7xqa6mtl2.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>a8cz6cu7e5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>av6w8kgt66.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>b9bk5wbcq9.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>bxvub5ada5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>c3frkrj4fj.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>cg4yq2srnc.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>cj5566h2ga.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>cs644xg564.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>dbu4b84rxf.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>dkc879ngq3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>dzg6xy7pwj.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ejvt5qm6ak.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>f73kdq92p3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>f7s53z58qe.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>feyaarzu9v.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>g28c52eehv.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>g2y4y55b64.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ggvn48r87g.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>glqzh8vgby.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>gta8lk7p23.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>hb56zgv37p.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>hdw39hrw9y.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>k674qkevps.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>kbmxgpxpgc.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>krvm3zuq6h.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>lr83yxwka7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>m297p6643m.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>m5mvw97r93.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>m8dbw4sv7c.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>mls7yz5dvl.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>mp6xlyr22a.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>mtkv5xtk9e.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>n66cz3y3bx.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>n9x2a789qt.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>nzq8sh4pbs.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>prcb7njmu6.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>pwdxu55a5a.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>qqp299437r.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>r45fhb6rf7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>rvh3l7un93.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>rx5hdcabgc.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>s69wq72ugq.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>su67r6k2v3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>tl55sbb4fm.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>u679fj5vs4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>v79kvwwj4g.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>vcra2ehyfk.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>w9q455wk68.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>wg4vff78zm.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>x44k69ngh6.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>x5l83yy675.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>x8jxxk4ff5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>x8uqf25wch.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>xy9t38ct57.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>y45688jllp.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>z24wtl6j62.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>zmvfpc5aq8.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>k6y4y55b64.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>t6d3zquu66.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9g2aggbj52.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>h65wbv5k3f.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>bvpn9ufa9b.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>hjevpa356n.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>6yxyv74ff7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>mqn7fxpca7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>7953jerfzd.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>qu637u8glc.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>g6gcrrvk4p.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>ln5gz23vtd.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>z959bm4gru.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>nu4557a4je.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>gvmwg8q7h5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>pu4na253f3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>yrqqpx2mcb.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>z4gj7hsk7h.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>bd757ywx3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>33r6p7g8nc.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>g69uk9uh2b.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>633vhxswh4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>tmhh9296z4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>zh3b7bxvad.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>xmn954pzmp.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>79w64w269u.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>d7g9azk84q.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>866k9ut3g3.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>2q884k2j68.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>gfat3222tu.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>pd25vrrwzn.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>y755zyxw56.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>qlbq5gtkt8.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>67369282zy.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>899vrgt9g8.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>mj797d8u6f.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>88k8774x49.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>t3b3f7n3x8.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>c7g47wypnu.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>z5b3gh5ugf.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>dd3a75yxkv.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>h5jmj969g5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>dr774724x4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>t7ky8fmwkd.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>fz2k2k5tej.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>w28pnjg2k4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>2rq3zucswp.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>vc83br9sjg.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>eqhxz8m8av.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>7k3cvf297u.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>7tnzynbdc7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>l6nv3x923s.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>h8vml93bkz.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>uzqba5354d.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>8qiegk9qfv.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>xx9sdjej2w.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>au67k4efj4.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>dmv22haz9p.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>7fbxrn65az.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>b55w3d8y8z.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>v7896pgt74.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>5ghnmfs3dh.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>627r9wr2y5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>sczv5946wb.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>8w3np9l82g.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>nrt9jy4kw9.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>dn942472g5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>cad8qz2s3j.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>r26jy69rpl.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>jb7bn6koa5.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>fkak3gfpt6.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>2tdux39lx8.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>3cgn6rq224.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>nfqy3847ph.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>dticjx1a9i.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>9wsyqb3ku7.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>x5854y7y24.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>6qx585k4p6.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>l93v5h6a4m.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>axh5283zss.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>5mv394q32t.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>SKAdNetworkIdentifier</key>
|
|
||||||
<string>x2jnk7ly8j.skadnetwork</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: e15412105e424ee0934897a153640a34
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 815571d7e09647a8b91951918d0feb7e
|
|
||||||
timeCreated: 1699597324
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
#if UNITY_IOS
|
|
||||||
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
using UnityEditor.iOS.Xcode;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.IO;
|
|
||||||
using Enumerable = System.Linq.Enumerable;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fireabse DebugView 开启参数注入
|
|
||||||
/// </summary>
|
|
||||||
public class IOSPostBuild_FireabseDebugView
|
|
||||||
{
|
|
||||||
public static readonly string Tag = "[POST]";
|
|
||||||
private static readonly string CodeFixMark = "CODE_FIX_BY_GURU";
|
|
||||||
private static readonly string CodeCmdArgsFix = $"\t\t//--------- {CodeFixMark} --------------\n\t\tNSMutableArray *newArguments = [NSMutableArray arrayWithArray:[[NSProcessInfo processInfo] arguments]];\n\t\t[newArguments addObject:@\"-FIRAnalyticsDebugEnabled\"];\n\t\t[newArguments addObject:@\"-FIRDebugEnabled\"];\n\t\t[[NSProcessInfo processInfo] setValue:[newArguments copy] forKey:@\"arguments\"];";
|
|
||||||
private static readonly string CodeDidFinishedLaunch =
|
|
||||||
"(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 需要在外部接口调用参数注入
|
|
||||||
/// </summary>
|
|
||||||
public static bool EnableDebugView = false; // 默认为False, 需要外部注入
|
|
||||||
|
|
||||||
[PostProcessBuild(1)]
|
|
||||||
public static void PostBuildXcodeArgs(BuildTarget target, string buildPath)
|
|
||||||
{
|
|
||||||
Debug.Log($"{Tag} --- Add Firebase debug args: {EnableDebugView}");
|
|
||||||
|
|
||||||
if (target != BuildTarget.iOS) return;
|
|
||||||
if (!EnableDebugView) return;
|
|
||||||
|
|
||||||
AddLauncherArgsToSchema(buildPath);
|
|
||||||
InjectLaunchCode(buildPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加启动参数到Scheme
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buildPath"></param>
|
|
||||||
private static void AddLauncherArgsToSchema(string buildPath)
|
|
||||||
{
|
|
||||||
string schemePath = buildPath + "/Unity-iPhone.xcodeproj/xcshareddata/xcschemes/Unity-iPhone.xcscheme";
|
|
||||||
|
|
||||||
var xcscheme = new XcScheme();
|
|
||||||
xcscheme.ReadFromFile(schemePath);
|
|
||||||
|
|
||||||
xcscheme.SetMetalValidationOnRun(XcScheme.MetalValidation.Extended);
|
|
||||||
xcscheme.SetFrameCaptureModeOnRun(XcScheme.FrameCaptureMode.Metal);
|
|
||||||
xcscheme.AddArgumentPassedOnLaunch("-FIRDebugEnabled");
|
|
||||||
|
|
||||||
xcscheme.WriteToFile(schemePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注入命令行参数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buildPath"></param>
|
|
||||||
private static void InjectLaunchCode(string buildPath)
|
|
||||||
{
|
|
||||||
string path = $"{buildPath}/Classes/UnityAppController.mm";
|
|
||||||
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
List<string> lines = Enumerable.ToList(File.ReadAllLines(path));
|
|
||||||
string line = "";
|
|
||||||
int idx = -1;
|
|
||||||
for (int i = 0; i < lines.Count; i++)
|
|
||||||
{
|
|
||||||
line = lines[i];
|
|
||||||
if (line.Contains(CodeDidFinishedLaunch))
|
|
||||||
{
|
|
||||||
// 找到注入行
|
|
||||||
idx = i + 1;
|
|
||||||
if (lines[idx].Contains("{"))
|
|
||||||
{
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
if (lines[idx].Contains(@"::printf(""-> applicationDidFinishLaunching()\n"");"))
|
|
||||||
{
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lines[idx].Contains(CodeFixMark) || lines[idx+1].Contains(CodeFixMark))
|
|
||||||
{
|
|
||||||
Debug.Log($"{Tag} <color=orange>---- code has already injected, skip... </color>");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines.Insert(idx, CodeCmdArgsFix);
|
|
||||||
File.WriteAllLines(path, lines.ToArray());
|
|
||||||
Debug.Log($"{Tag} <color=#88ff00>---- code has success injected.</color> path:\n{path}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Log($"{Tag} <color=red>---- file not found: {path}, inject failed... </color>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 7f98aa3ce0d04b2d9f8442a0b288f27e
|
|
||||||
timeCreated: 1699597308
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
#if UNITY_IOS
|
|
||||||
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using UnityEditor;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
|
|
||||||
|
|
||||||
public class IOSPostBuild_Firebase_VersionFix
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
private static string[] fixedLibs = new string[]
|
|
||||||
{
|
|
||||||
"Firebase/Core",
|
|
||||||
"Firebase/Firestore",
|
|
||||||
"Firebase/Analytics",
|
|
||||||
"Firebase/Storage",
|
|
||||||
"Firebase/Auth",
|
|
||||||
"Firebase/Messaging",
|
|
||||||
"Firebase/Crashlytics",
|
|
||||||
"Firebase/DynamicLinks",
|
|
||||||
"Firebase/RemoteConfig",
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string[] additionLibs = new string[]
|
|
||||||
{
|
|
||||||
"FirebaseFirestoreInternal",
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string fixedVersion = "10.22.0";
|
|
||||||
private static string minTargetSdk = "8.0";
|
|
||||||
|
|
||||||
// <iosPod name="Firebase/Core" version="10.22.0" minTargetSdk="8.0" />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Firebase 10.20.0 fixed to 10.22.0. BUT higher version do not open this ATTRIBUTE !!
|
|
||||||
// [PostProcessBuild(47)] // MAX POD Process Order
|
|
||||||
public static void PostBuildFixPodDeps(BuildTarget target, string projPath)
|
|
||||||
{
|
|
||||||
if (target != BuildTarget.iOS) return;
|
|
||||||
|
|
||||||
string podfile = Path.Combine(projPath, "Podfile");
|
|
||||||
if (!File.Exists(podfile)) return;
|
|
||||||
|
|
||||||
FixFirebasePodVersion(podfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void FixFirebasePodVersion(string podfile)
|
|
||||||
{
|
|
||||||
var lines = File.ReadAllLines(podfile).ToList();
|
|
||||||
string line = "";
|
|
||||||
int idx = 0;
|
|
||||||
bool isDirty = false;
|
|
||||||
List<string> needAdded = new List<string>(additionLibs);
|
|
||||||
for (int i = 0; i < lines.Count; i++)
|
|
||||||
{
|
|
||||||
line = lines[i];
|
|
||||||
|
|
||||||
if (line.Contains("pod '"))
|
|
||||||
{
|
|
||||||
idx = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var libName in fixedLibs)
|
|
||||||
{
|
|
||||||
if (line.Contains(libName))
|
|
||||||
{
|
|
||||||
lines[i] = FixOneFirebaseLibVersion(line, fixedVersion);
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var libName in additionLibs)
|
|
||||||
{
|
|
||||||
if (line.Contains(libName))
|
|
||||||
{
|
|
||||||
needAdded.Remove(libName);
|
|
||||||
lines[i] = FixOneFirebaseLibVersion(line, fixedVersion);
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needAdded.Count > 0)
|
|
||||||
{
|
|
||||||
// pod 'Firebase/DynamicLinks', '10.20.0'
|
|
||||||
foreach (var libName in needAdded)
|
|
||||||
{
|
|
||||||
idx++;
|
|
||||||
idx = Mathf.Min(idx, lines.Count - 1);
|
|
||||||
lines.Insert(idx, $"\tpod '{libName}', '{fixedVersion}'");
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isDirty) File.WriteAllLines(podfile, lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FixOneFirebaseLibVersion(string line, string fixedVersion)
|
|
||||||
{
|
|
||||||
if(!line.Contains("', '") || !line.Contains("pod")) return line;
|
|
||||||
string fixedLine = line.Substring(0, line.IndexOf("', '") + 4) + $"{fixedVersion}'";
|
|
||||||
return fixedLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 2aab28d86c8346e581c650a86cad060f
|
|
||||||
timeCreated: 1717039005
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
#if UNITY_IOS
|
|
||||||
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
using UnityEditor.iOS.Xcode;
|
|
||||||
using UnityEditor.iOS.Xcode.Extensions;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
public static class IOSPostProcessBuildIPM
|
|
||||||
{
|
|
||||||
public static readonly string DEFAULT_PROJECT_TARGET_NAME = "Unity-iPhone";
|
|
||||||
public static readonly string NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME = "U3D2FCM-iOS";
|
|
||||||
public static readonly string NOTIFICATION_SERVICE_EXTENSION_OBJECTIVEC_FILENAME = "NotificationService";
|
|
||||||
|
|
||||||
private static readonly char DIR_CHAR = Path.DirectorySeparatorChar;
|
|
||||||
public static readonly string OS_PLATFORM_LOCATION = $"Assets/Guru/GuruBuildTool/Editor/IOS_POST_IPM/";
|
|
||||||
private static readonly string SKADNetworkIdentifier = "SKAdNetworkIdentifier";
|
|
||||||
private static List<string> NETWORK_IDENTIFIER_ARRAY = new List<string>();
|
|
||||||
|
|
||||||
private enum EntitlementOptions {
|
|
||||||
AppGroups,
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly string[] FRAMEWORKS_MAIN_TO_ADD = {
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly string[] FRAMEWORKS_UNITY_FRAMEWORK_TO_ADD = {
|
|
||||||
"GameKit.framework",
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly string[] FRAMEWORKS_FCM_TO_ADD = {
|
|
||||||
"UserNotifications.framework",
|
|
||||||
"UIKit.framework",
|
|
||||||
};
|
|
||||||
|
|
||||||
[PostProcessBuild(1)]
|
|
||||||
private static void OnPostProcessBuild(BuildTarget buildTarget, string path)
|
|
||||||
{
|
|
||||||
if (buildTarget != BuildTarget.iOS)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var projectPath = PBXProject.GetPBXProjectPath(path);
|
|
||||||
var project = new PBXProject();
|
|
||||||
project.ReadFromString(File.ReadAllText(projectPath));
|
|
||||||
|
|
||||||
var mainTargetName = GetPBXProjectTargetName(project);
|
|
||||||
var mainTargetGUID = GetPBXProjectTargetGUID(project);
|
|
||||||
var unityFrameworkGUID = GetPBXProjectUnityFrameworkGUID(project);
|
|
||||||
|
|
||||||
foreach(var framework in FRAMEWORKS_MAIN_TO_ADD) {
|
|
||||||
project.AddFrameworkToProject(mainTargetGUID, framework, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(var framework in FRAMEWORKS_UNITY_FRAMEWORK_TO_ADD) {
|
|
||||||
project.AddFrameworkToProject(unityFrameworkGUID, framework, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModifyPlistFile(path);
|
|
||||||
|
|
||||||
// 关闭Bitode
|
|
||||||
project.SetBuildProperty(mainTargetGUID, "ENABLE_BITCODE", "NO");
|
|
||||||
project.SetBuildProperty(unityFrameworkGUID, "ENABLE_BITCODE", "NO");
|
|
||||||
|
|
||||||
// 添加 UnityFramework 版本号
|
|
||||||
project.SetBuildProperty(unityFrameworkGUID, "CURRENT_PROJECT_VERSION", PlayerSettings.bundleVersion);
|
|
||||||
project.SetBuildProperty(unityFrameworkGUID, "MARKETING_VERSION", PlayerSettings.iOS.buildNumber);
|
|
||||||
|
|
||||||
AddOrUpdateEntitlements(path, project, mainTargetGUID, mainTargetName,
|
|
||||||
new HashSet<EntitlementOptions>
|
|
||||||
{
|
|
||||||
EntitlementOptions.AppGroups
|
|
||||||
});
|
|
||||||
|
|
||||||
// AddNotificationServiceExtension(project ,path); // <--- 无需添加Extension了
|
|
||||||
|
|
||||||
project.WriteToFile(projectPath);
|
|
||||||
var contents = File.ReadAllText(projectPath);
|
|
||||||
project.ReadFromString(contents);
|
|
||||||
|
|
||||||
// Add push notifications as a capability on the main app target
|
|
||||||
AddPushCapability(project, path, mainTargetGUID, mainTargetName);
|
|
||||||
|
|
||||||
project.SetBuildProperty(unityFrameworkGUID, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES");
|
|
||||||
project.SetBuildProperty(mainTargetGUID, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES");
|
|
||||||
File.WriteAllText(projectPath, project.WriteToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ModifyPlistFile(string pathToBuildProject)
|
|
||||||
{
|
|
||||||
var plistPath = Path.Combine(pathToBuildProject, "Info.plist");
|
|
||||||
var plist = new PlistDocument();
|
|
||||||
plist.ReadFromFile(plistPath);
|
|
||||||
//设置Google AD GADApplicationIdentifier
|
|
||||||
plist.root.SetString("NSCalendarsUsageDescription", "Store calendar events from ads");
|
|
||||||
// plist.root.SetString("GADApplicationIdentifier", "ca-app-pub-2436733915645843~7788635385");
|
|
||||||
// plist.root.SetString("FacebookClientToken", "2414c9079473645856a5ef6b8ac95cf6");
|
|
||||||
// plist.root.SetString("FacebookDisplayName", PlayerSettings.productName);
|
|
||||||
//设置Xcode的Att弹窗配置
|
|
||||||
plist.root.SetString("NSUserTrackingUsageDescription","By allowing tracking, we'll be able to better tailor ads served to you on this game.");
|
|
||||||
//设置SKAdNetworkItems
|
|
||||||
// ReadSKADNetworkPlistFile();
|
|
||||||
// var plistElementArray = plist.root.CreateArray("SKAdNetworkItems");
|
|
||||||
// AddPlatformADNetworkIdentifier(plistElementArray, NETWORK_IDENTIFIER_ARRAY);
|
|
||||||
|
|
||||||
// 设置合规出口证明
|
|
||||||
plist.root.SetBoolean("ITSAppUsesNonExemptEncryption", false);
|
|
||||||
|
|
||||||
var root = plist.root.values;
|
|
||||||
PlistElement atsRoot;
|
|
||||||
root.TryGetValue("NSAppTransportSecurity", out atsRoot);
|
|
||||||
|
|
||||||
if (atsRoot == null || atsRoot.GetType() != typeof(PlistElementDict))
|
|
||||||
{
|
|
||||||
atsRoot = plist.root.CreateDict("NSAppTransportSecurity");
|
|
||||||
atsRoot.AsDict().SetBoolean("NSAllowsArbitraryLoads", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
var atsRootDict = atsRoot.AsDict().values;
|
|
||||||
if (atsRootDict.ContainsKey("NSAllowsArbitraryLoadsInWebContent"))
|
|
||||||
{
|
|
||||||
atsRootDict.Remove("NSAllowsArbitraryLoadsInWebContent");
|
|
||||||
}
|
|
||||||
|
|
||||||
plist.WriteToFile(plistPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region 纯功能函数
|
|
||||||
|
|
||||||
private static void AddOrUpdateEntitlements(string path, PBXProject project, string targetGUI,
|
|
||||||
string targetName, HashSet<EntitlementOptions> options)
|
|
||||||
{
|
|
||||||
string entitlementPath = GetEntitlementsPath(path, project, targetGUI, targetName);
|
|
||||||
var entitlements = new PlistDocument();
|
|
||||||
|
|
||||||
// Check if the file already exisits and read it
|
|
||||||
if (File.Exists(entitlementPath)) {
|
|
||||||
entitlements.ReadFromFile(entitlementPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TOOD: This can be updated to use project.AddCapability() in the future
|
|
||||||
if (options.Contains(EntitlementOptions.AppGroups) && entitlements.root["com.apple.security.application-groups"] == null) {
|
|
||||||
var groups = entitlements.root.CreateArray("com.apple.security.application-groups");
|
|
||||||
groups.AddString("group." + PlayerSettings.applicationIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
entitlements.WriteToFile(entitlementPath);
|
|
||||||
|
|
||||||
// Copy the entitlement file to the xcode project
|
|
||||||
var entitlementFileName = Path.GetFileName(entitlementPath);
|
|
||||||
var relativeDestination = targetName + "/" + entitlementFileName;
|
|
||||||
|
|
||||||
// Add the pbx configs to include the entitlements files on the project
|
|
||||||
project.AddFile(relativeDestination, entitlementFileName);
|
|
||||||
project.AddBuildProperty(targetGUI, "CODE_SIGN_ENTITLEMENTS", relativeDestination);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddPushCapability(PBXProject project, string path, string targetGUID, string targetName)
|
|
||||||
{
|
|
||||||
var projectPath = PBXProject.GetPBXProjectPath(path);
|
|
||||||
//project.AddCapability(targetGUID, PBXCapabilityType.PushNotifications);
|
|
||||||
//project.AddCapability(targetGUID, PBXCapabilityType.BackgroundModes);
|
|
||||||
|
|
||||||
var entitlementsPath = GetEntitlementsPath(path, project, targetGUID, targetName);
|
|
||||||
// NOTE: ProjectCapabilityManager's 4th constructor param requires Unity 2019.3+
|
|
||||||
var projCapability = new ProjectCapabilityManager(projectPath, entitlementsPath, targetName);
|
|
||||||
//projCapability.AddBackgroundModes(BackgroundModesOptions.BackgroundFetch);
|
|
||||||
//projCapability.AddBackgroundModes(BackgroundModesOptions.RemoteNotifications);
|
|
||||||
projCapability.AddPushNotifications(false);
|
|
||||||
projCapability.WriteToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static string GetPBXProjectTargetName(PBXProject project)
|
|
||||||
{
|
|
||||||
// var projectUUID = project.GetUnityMainTargetGuid();
|
|
||||||
// return project.GetBuildPhaseName(projectUUID);
|
|
||||||
// The above always returns null, using a static value for now.
|
|
||||||
return DEFAULT_PROJECT_TARGET_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetPBXProjectTargetGUID(PBXProject project)
|
|
||||||
{
|
|
||||||
return project.GetUnityMainTargetGuid();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetPBXProjectUnityFrameworkGUID(PBXProject project)
|
|
||||||
{
|
|
||||||
return project.GetUnityFrameworkTargetGuid();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetEntitlementsPath(string path, PBXProject project, string targetGUI, string targetName)
|
|
||||||
{
|
|
||||||
// Check if there is already an eltitlements file configured in the Xcode project
|
|
||||||
var relativeEntitlementPath = project.GetBuildPropertyForConfig(targetGUI, "CODE_SIGN_ENTITLEMENTS");
|
|
||||||
if (relativeEntitlementPath != null) {
|
|
||||||
var entitlementPath = path + DIR_CHAR + relativeEntitlementPath;
|
|
||||||
if (File.Exists(entitlementPath)) {
|
|
||||||
return entitlementPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No existing file, use a new name
|
|
||||||
return path + DIR_CHAR + targetName + DIR_CHAR + targetName + ".entitlements";
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 963cba5e4a074d208e7d9343acb547dc
|
|
||||||
timeCreated: 1632375624
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>U3D2FCM-iOS</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.1.0</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>8</string>
|
|
||||||
<key>NSExtension</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExtensionPointIdentifier</key>
|
|
||||||
<string>com.apple.usernotifications.service</string>
|
|
||||||
<key>NSExtensionPrincipalClass</key>
|
|
||||||
<string>NotificationService</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: c76614c2deb1e4591a5f2f0d5cdfabf7
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
//
|
|
||||||
// NotificationService.h
|
|
||||||
// U3D2FCM-iOS
|
|
||||||
//
|
|
||||||
// Created by Xiaohang Yang on 2021/1/28.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UserNotifications/UserNotifications.h>
|
|
||||||
|
|
||||||
@interface NotificationService : UNNotificationServiceExtension
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
//
|
|
||||||
// NotificationService.m
|
|
||||||
// U3D2FCM
|
|
||||||
//
|
|
||||||
// Created by Michael on 2020/11/27.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "NotificationService.h"
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
|
|
||||||
@interface NotificationService ()
|
|
||||||
|
|
||||||
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
|
|
||||||
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@implementation NotificationService
|
|
||||||
|
|
||||||
|
|
||||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
|
|
||||||
self.contentHandler = contentHandler;
|
|
||||||
self.bestAttemptContent = [request.content mutableCopy];
|
|
||||||
|
|
||||||
NSLog(@"EventPush-NotificationService-didReceiveNotificationRequest");
|
|
||||||
|
|
||||||
NSString *packageName = @"";
|
|
||||||
NSArray *arr = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"] componentsSeparatedByString:@"."];
|
|
||||||
for(int i=0;i<arr.count-1;i++){
|
|
||||||
if(i==arr.count-2){
|
|
||||||
packageName = [packageName stringByAppendingString:arr[i]];
|
|
||||||
}else{
|
|
||||||
packageName = [packageName stringByAppendingString: [NSString stringWithFormat:@"%@.",arr[i]]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NSLog(@"EventPush-NotificationService-packageName: %@", packageName);
|
|
||||||
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName: [NSString stringWithFormat: @"group.%@", packageName]];
|
|
||||||
NSString *appCountry= [defaults stringForKey:@"appCountry"];
|
|
||||||
NSString *appIdentifier = [defaults stringForKey:@"appIdentifier"];
|
|
||||||
NSString *appVersion = [defaults stringForKey:@"appVersion"];
|
|
||||||
NSString *deviceCountry = [defaults stringForKey:@"deviceCountry"];
|
|
||||||
NSString *deviceId = [defaults stringForKey:@"deviceId"];
|
|
||||||
NSString *deviceToken = [defaults stringForKey:@"deviceToken"];
|
|
||||||
NSString *eventUrl = [defaults stringForKey:@"eventUrl"];
|
|
||||||
NSString *IPM_X_APP_ID = [defaults stringForKey:@"IPM_X_APP_ID"];
|
|
||||||
NSString *IPM_TOKEN = [defaults stringForKey:@"IPM_TOKEN"];
|
|
||||||
NSString *IPM_UID = [defaults stringForKey:@"IPM_UID"];
|
|
||||||
|
|
||||||
//timezone
|
|
||||||
NSTimeZone *zone = [NSTimeZone localTimeZone];
|
|
||||||
NSString *timezone = [zone name];
|
|
||||||
//model
|
|
||||||
UIDevice *currentDevice = [UIDevice currentDevice];
|
|
||||||
NSString *model = [currentDevice model];
|
|
||||||
//language
|
|
||||||
NSArray *languageArray = [NSLocale preferredLanguages];
|
|
||||||
NSString *language = [languageArray objectAtIndex:0];
|
|
||||||
//locale
|
|
||||||
NSLocale *localeObj = [NSLocale currentLocale];
|
|
||||||
NSString *locale = [localeObj localeIdentifier];
|
|
||||||
|
|
||||||
NSDate *currentDate = [[NSDate alloc] init];
|
|
||||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
|
||||||
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
|
|
||||||
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
|
|
||||||
NSString *appEventTime = [dateFormatter stringFromDate:currentDate];
|
|
||||||
|
|
||||||
NSString *deviceData = [NSString stringWithFormat: @"{\"androidId\":null,\"appCountry\":\"%@\",\"appIdentifier\":\"%@\",\"appVersion\":\"%@\",\"brand\":null,\"deviceCoordinates\":{\"latitude\":0,\"longitude\":0},\"deviceCountry\":\"%@\",\"deviceId\":\"%@\",\"deviceToken\":\"%@\",\"deviceType\":\"iOS\",\"gpsCoordinates\":{\"latitude\":0,\"longitude\":0},\"groups\":null,\"language\":\"%@\",\"locale\":\"%@\",\"model\":\"%@\",\"pushDeviceType\":\"iOS\",\"pushNotificationEnable\":true,\"pushNotifications\":null,\"pushType\":\"FCM\",\"timezone\":\"%@\",\"uid\":\"%@\"}", appCountry,appIdentifier,appVersion,deviceCountry,deviceId,deviceToken,language,locale,model,timezone,IPM_UID];
|
|
||||||
NSString *postData = [NSString stringWithFormat: @"{\"appEventTime\":\"%@\",\"deviceData\":%@, \"eventType\":\"DeviceReceive\",\"serverParams\":\"{\\\"itemIndex\\\":0,\\\"pushEventId\\\":\\\"test123\\\",\\\"serverPushTime\\\":\\\"2020-11-27T08:48:39Z\\\",\\\"silent\\\":true,\\\"taskName\\\":\\\"pushTest-dof\\\"}\" }",appEventTime,deviceData];
|
|
||||||
|
|
||||||
|
|
||||||
NSLog(@"EventPush-NotificationService-PlayerPrefs: %@", postData);
|
|
||||||
NSLog(@"EventPush-NotificationService-eventUrl: %@", eventUrl);
|
|
||||||
|
|
||||||
NSURL *url = [NSURL URLWithString:eventUrl];
|
|
||||||
NSMutableURLRequest *httpRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
|
|
||||||
NSData *requestData = [postData dataUsingEncoding:NSUTF8StringEncoding];
|
|
||||||
[httpRequest setHTTPMethod:@"POST"];
|
|
||||||
[httpRequest setValue:IPM_X_APP_ID forHTTPHeaderField:@"X-APP-ID"];
|
|
||||||
[httpRequest setValue:IPM_TOKEN forHTTPHeaderField:@"X-ACCESS-TOKEN"];
|
|
||||||
[httpRequest setValue:IPM_UID forHTTPHeaderField:@"X-UID"];
|
|
||||||
[httpRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
|
||||||
|
|
||||||
[httpRequest setValue:[NSString stringWithFormat:@"%d", [requestData length]] forHTTPHeaderField:@"Content-Length"];
|
|
||||||
[httpRequest setHTTPBody: requestData];
|
|
||||||
|
|
||||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
||||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
|
||||||
__block NSURLSessionDataTask *task = [session dataTaskWithRequest:httpRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
|
||||||
if (error!=nil)
|
|
||||||
{
|
|
||||||
[task suspend];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NSString *requestReply = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
|
|
||||||
NSLog(@"EventPush-NotificationService-response: %@", requestReply);
|
|
||||||
[task suspend];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
[task resume];
|
|
||||||
|
|
||||||
// Modify the notification content here...
|
|
||||||
//self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
|
|
||||||
|
|
||||||
self.contentHandler(self.bestAttemptContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)serviceExtensionTimeWillExpire {
|
|
||||||
// Called just before the extension will be terminated by the system.
|
|
||||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
|
||||||
self.contentHandler(self.bestAttemptContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
#if UNITY_IOS
|
|
||||||
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using System.IO;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
using Debug = UnityEngine.Debug;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 针对AmazonSDK iOS平台构建后
|
|
||||||
/// BitCode报错的问题
|
|
||||||
/// </summary>
|
|
||||||
public class IOSPostProcessBuild_PodFix
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加内容
|
|
||||||
/// </summary>
|
|
||||||
private static readonly string MOD_SCRIPT = @"#Compile bugs fixed by HuYufei 2023-11-16
|
|
||||||
post_install do |installer|
|
|
||||||
installer.pods_project.targets.each do |target|
|
|
||||||
target.build_configurations.each do |config|
|
|
||||||
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
|
||||||
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
|
|
||||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
|
||||||
xcconfig_path = config.base_configuration_reference.real_path
|
|
||||||
xcconfig = File.read(xcconfig_path)
|
|
||||||
xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, ""TOOLCHAIN_DIR"")
|
|
||||||
File.open(xcconfig_path, ""w"") { |file| file << xcconfig_mod }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构建操作
|
|
||||||
/// 构建顺序 45-50 可以保证执行时序在MAX 自身生成podfile之后, 注入需要的逻辑
|
|
||||||
/// AmazonSDK使用了45, 工具设为46,确保后发执行
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="target"></param>
|
|
||||||
/// <param name="projPath"></param>
|
|
||||||
[PostProcessBuild(46)]
|
|
||||||
private static void OnPostProcessBuild(BuildTarget target, string projPath)
|
|
||||||
{
|
|
||||||
if (target != BuildTarget.iOS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
string podPath = Path.Combine(projPath, "Podfile");
|
|
||||||
if (File.Exists(podPath))
|
|
||||||
{
|
|
||||||
bool needFix = false;
|
|
||||||
string content = File.ReadAllText(podPath);
|
|
||||||
if (!content.Contains("#BITCODE"))
|
|
||||||
{
|
|
||||||
content = content + "\n" + MOD_SCRIPT;
|
|
||||||
File.WriteAllText(podPath, content);
|
|
||||||
Debug.Log($"<color=#88ff00>=== Fix Pods BitCode bug ===</color>");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError($"=== POD not exists, exit pod hook...===");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 7c29eec7a52a4f8a9e8638eaa0985442
|
|
||||||
timeCreated: 1715317220
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
#if UNITY_IOS
|
|
||||||
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEditor.iOS.Xcode;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
public class IOSPostProcessBuild_PrivacyInfo
|
|
||||||
{
|
|
||||||
private const string XCPrivacyInfo = "PrivacyInfo.xcprivacy";
|
|
||||||
private const string DefaultWorkdir = "Guru/BuildTools/Editor/IOS_POST_PRIVACYINFO";
|
|
||||||
private const string SourceFileName = "PrivacyInfo.plist";
|
|
||||||
private static string IosPrivacyInfoPath => $"{Application.dataPath}/Plugins/iOS/{SourceFileName}";
|
|
||||||
|
|
||||||
[PostProcessBuild(200)]
|
|
||||||
public static void OnPostProcessBuild(BuildTarget target, string buildPath)
|
|
||||||
{
|
|
||||||
if (target == BuildTarget.iOS)
|
|
||||||
{
|
|
||||||
AddPrivacyInfo(buildPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 向 XCode 添加隐私清单文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buildPath"></param>
|
|
||||||
private static void AddPrivacyInfo(string buildPath)
|
|
||||||
{
|
|
||||||
if (CheckEvn())
|
|
||||||
{
|
|
||||||
var xcprojPath = PBXProject.GetPBXProjectPath(buildPath);
|
|
||||||
var xcproj = new PBXProject();
|
|
||||||
xcproj.ReadFromFile(xcprojPath);
|
|
||||||
|
|
||||||
var dest = $"{buildPath}/{XCPrivacyInfo}";
|
|
||||||
FileUtil.ReplaceFile(IosPrivacyInfoPath, dest);
|
|
||||||
|
|
||||||
var mainTarget = xcproj.GetUnityMainTargetGuid();
|
|
||||||
var guid = xcproj.AddFile(dest,$"{XCPrivacyInfo}", PBXSourceTree.Source);
|
|
||||||
|
|
||||||
xcproj.AddFileToBuild(mainTarget, guid);
|
|
||||||
xcproj.WriteToFile(xcprojPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError("Inject iOS PrivacyInfo failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 工作目录
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static string GetWorkdir()
|
|
||||||
{
|
|
||||||
var guids = AssetDatabase.FindAssets($"{nameof(IOSPostProcessBuild_PrivacyInfo)}");
|
|
||||||
if (guids.Length > 0)
|
|
||||||
{
|
|
||||||
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
|
||||||
var dir = Directory.GetParent(path).FullName;
|
|
||||||
if (Directory.Exists(dir)) return dir;
|
|
||||||
}
|
|
||||||
return DefaultWorkdir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查环境
|
|
||||||
/// </summary>
|
|
||||||
private static bool CheckEvn()
|
|
||||||
{
|
|
||||||
if (File.Exists(IosPrivacyInfoPath)) return true;
|
|
||||||
|
|
||||||
var workdir = GetWorkdir();
|
|
||||||
var source = $"{workdir}/{SourceFileName}";
|
|
||||||
var toDir = Directory.GetParent(IosPrivacyInfoPath);
|
|
||||||
if (!toDir.Exists) toDir.Create();
|
|
||||||
if (File.Exists(source))
|
|
||||||
{
|
|
||||||
FileUtil.ReplaceFile(source, IosPrivacyInfoPath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.LogError($"--- PrivacyInfo.plist not found,Check file path:{source}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 3405624b18974efa8564223e50bf0c55
|
|
||||||
timeCreated: 1713372443
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyTracking</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrivacyTrackingDomains</key>
|
|
||||||
<array>
|
|
||||||
<string>https://consent.adjust.com</string>
|
|
||||||
<string>https://consent.adjust.net.in</string>
|
|
||||||
<string>https://consent.adjust.world</string>
|
|
||||||
<string>https://consent.adjust.cn</string>
|
|
||||||
<string>https://consent.eu.adjust.com</string>
|
|
||||||
<string>https://consent.tr.adjust.com</string>
|
|
||||||
<string>https://consent.us.adjust.com</string>
|
|
||||||
</array>
|
|
||||||
<key>NSPrivacyAccessedAPITypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>CA92.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>C617.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>NSPrivacyCollectedDataTypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeAdvertisingData</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeDeveloperAdvertising</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeOtherDataTypes</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeCrashData</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeOtherUsageData</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeAdvertisingData</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataType</key>
|
|
||||||
<string>NSPrivacyCollectedDataTypeUserID</string>
|
|
||||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
||||||
<array>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
||||||
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: f04c7fe558ae4a1bbb278ed19c5be0f2
|
|
||||||
timeCreated: 1715315327
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: a78e5d32ead914b3f9f0a718da8594ea
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
#if UNITY_IOS
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.iOS.Xcode;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Guru
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// SWIFT语言支持
|
|
||||||
/// </summary>
|
|
||||||
public class IOSPostBuildSwift
|
|
||||||
{
|
|
||||||
[PostProcessBuild(2000)]
|
|
||||||
public static void OnPostProcessBuild(BuildTarget target, string buildPath)
|
|
||||||
{
|
|
||||||
if (target != BuildTarget.iOS) return;
|
|
||||||
|
|
||||||
Debug.Log($"--- Add Swift support to project: {buildPath}");
|
|
||||||
|
|
||||||
// 更新Swift语言支持
|
|
||||||
AddSwiftSupport(buildPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加Swift Support
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buildPath"></param>
|
|
||||||
private static void AddSwiftSupport(string buildPath)
|
|
||||||
{
|
|
||||||
var projectPath = PBXProject.GetPBXProjectPath(buildPath);
|
|
||||||
var project = new PBXProject();
|
|
||||||
project.ReadFromFile(projectPath);
|
|
||||||
var mainTargetGuid = project.GetUnityMainTargetGuid();
|
|
||||||
var frameworkTargetGuid = project.GetUnityFrameworkTargetGuid();
|
|
||||||
|
|
||||||
// 关闭BitCode
|
|
||||||
project.SetBuildProperty(mainTargetGuid, "ENABLE_BITCODE", "NO");
|
|
||||||
project.SetBuildProperty(frameworkTargetGuid, "ENABLE_BITCODE", "NO");
|
|
||||||
|
|
||||||
// 添加搜索路径
|
|
||||||
project.AddBuildProperty(frameworkTargetGuid, "LD_RUNPATH_SEARCH_PATHS", "/usr/lib/swift");
|
|
||||||
|
|
||||||
// 设置主项目的SWIFT构建支持
|
|
||||||
project.SetBuildProperty(mainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
|
|
||||||
project.SetBuildProperty(frameworkTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "NO");
|
|
||||||
|
|
||||||
project.WriteToFile(projectPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 661b372db3634542856a44e37664649c
|
|
||||||
timeCreated: 1673406971
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: a0af2d1a3be34a4cb6101bd2a40b4c36
|
|
||||||
timeCreated: 1705373808
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#if UNITY_ANDROID
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEditor.Android;
|
|
||||||
using UnityEngine;
|
|
||||||
using Debug=UnityEngine.Debug;
|
|
||||||
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
public class AndroidGradleOutputDeps: IPostGenerateGradleAndroidProject
|
|
||||||
{
|
|
||||||
public int callbackOrder => 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 生成Android项目后执行逻辑
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buildPath"></param>
|
|
||||||
public void OnPostGenerateGradleAndroidProject(string buildPath)
|
|
||||||
{
|
|
||||||
Debug.Log($"<color=#88ff00>---- Android Projct start build {buildPath}</color>");
|
|
||||||
DepsOutputHelper.InstallAndRun(buildPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: f8b64715449c4f23a7781dc15f5d38fe
|
|
||||||
timeCreated: 1702349739
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using UnityEditor;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using UnityEngine;
|
|
||||||
using Debug=UnityEngine.Debug;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public class DepsOutputHelper
|
|
||||||
{
|
|
||||||
public static readonly string DepsScriptName = "deps.sh";
|
|
||||||
public static readonly string EnvScriptName = ".deps_env";
|
|
||||||
private static string _scriptFilePath = String.Empty;
|
|
||||||
public static string ScriptFilePath
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(string.IsNullOrEmpty(_scriptFilePath))
|
|
||||||
_scriptFilePath = GetScriptFilePath();
|
|
||||||
return _scriptFilePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// 获取脚本路径
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static string GetScriptFilePath()
|
|
||||||
{
|
|
||||||
string sc = string.Empty;
|
|
||||||
var guids = AssetDatabase.FindAssets($"{nameof(DepsOutputHelper)} t:script");
|
|
||||||
if (guids.Length > 0)
|
|
||||||
{
|
|
||||||
sc = AssetDatabase.GUIDToAssetPath(guids[0]);
|
|
||||||
var fpath = $"{Directory.GetParent(sc).FullName}/files/{DepsScriptName}";
|
|
||||||
if(File.Exists(fpath)) return fpath;
|
|
||||||
}
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 执行脚本
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="projPath"></param>
|
|
||||||
/// <param name="cmd"></param>
|
|
||||||
public static void CallDepsScript(string workpath, string cmd = "")
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(cmd)) cmd = DepsScriptName;
|
|
||||||
RunShellCmd(workpath, cmd);
|
|
||||||
Debug.Log($"---- running command: {cmd} is over -----");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 运行命令
|
|
||||||
public static void RunShellCmd(string workpath, string cmd)
|
|
||||||
{
|
|
||||||
//------ 启动命令 --------
|
|
||||||
Process p = new Process();
|
|
||||||
p.StartInfo.WorkingDirectory = workpath;
|
|
||||||
p.StartInfo.FileName = "/bin/bash";
|
|
||||||
p.StartInfo.Arguments = cmd;
|
|
||||||
p.StartInfo.UseShellExecute = false;
|
|
||||||
p.StartInfo.RedirectStandardOutput = true;
|
|
||||||
p.Start();
|
|
||||||
var log = p.StandardOutput.ReadToEnd();
|
|
||||||
p.WaitForExit();
|
|
||||||
Debug.Log(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置ENV文件
|
|
||||||
private static void SetupEnvScript(string projPath, string depauditPath = "")
|
|
||||||
{
|
|
||||||
string buildName = $"1.0.0-00000000";
|
|
||||||
string platform = $"editor";
|
|
||||||
string dir = projPath;
|
|
||||||
|
|
||||||
#if UNITY_ANDROID
|
|
||||||
buildName = $"{Application.version}-{PlayerSettings.Android.bundleVersionCode}";
|
|
||||||
platform = "android";
|
|
||||||
#elif UNITY_IOS
|
|
||||||
buildName = $"{Application.version}-{PlayerSettings.iOS.buildNumber}";
|
|
||||||
platform = "ios";
|
|
||||||
#endif
|
|
||||||
List<string> lines = new List<string>()
|
|
||||||
{
|
|
||||||
$"export BUILD_NAME={buildName}",
|
|
||||||
$"export APP_NAME=\"{PlayerSettings.productName}\"",
|
|
||||||
$"export APP_ID={Application.identifier}",
|
|
||||||
$"export PLATFORM={platform}",
|
|
||||||
$"export DIR={dir}",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(depauditPath))
|
|
||||||
{
|
|
||||||
// 本地调试, 需要工具路径
|
|
||||||
lines.Add($"export depaudit={depauditPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllLines($"{projPath}/{EnvScriptName}", lines.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 安装和运行依赖输出器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buildPath"></param>
|
|
||||||
public static void InstallAndRun(string buildPath)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(ScriptFilePath) || !File.Exists(ScriptFilePath))
|
|
||||||
{
|
|
||||||
Debug.LogError($"--- deps script file not found, skip output deps...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string projPath = buildPath;
|
|
||||||
#if UNITY_ANDROID
|
|
||||||
projPath = Directory.GetParent(buildPath).FullName;
|
|
||||||
#elif UNITY_IOS
|
|
||||||
//TBD
|
|
||||||
#endif
|
|
||||||
//---- Setup Env ----
|
|
||||||
SetupEnvScript(projPath);
|
|
||||||
|
|
||||||
//---- Setup Deps ----
|
|
||||||
string to = $"{projPath}/{DepsScriptName}";
|
|
||||||
if (File.Exists(to)) File.Delete(to);
|
|
||||||
FileUtil.CopyFileOrDirectory(ScriptFilePath, to); //拷贝脚本
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Debug.Log($"=== Output build deps data ===");
|
|
||||||
CallDepsScript(projPath);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.LogError(ex);
|
|
||||||
Debug.Log($"=== Output pods deps failed: {ex}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 831ffddddd78421b8597eac6367fd920
|
|
||||||
timeCreated: 1702366162
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#if UNITY_IOS
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Guru.Editor
|
|
||||||
{
|
|
||||||
public class IOSXcodeOutputDeps
|
|
||||||
{
|
|
||||||
// <summary>
|
|
||||||
/// 构建操作
|
|
||||||
/// 构建顺序 45-50 可以保证执行时序在MAX 自身生成podfile之后, 注入需要的逻辑
|
|
||||||
/// AmazonSDK使用了45, 工具设为 > 45, 确保后发执行
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="target"></param>
|
|
||||||
/// <param name="projPath"></param>
|
|
||||||
[PostProcessBuild(1000)]
|
|
||||||
private static void OnPostProcessBuild(BuildTarget target, string projPath)
|
|
||||||
{
|
|
||||||
string podlock = Path.Combine(projPath, "Podfile.lock");
|
|
||||||
if (File.Exists(podlock))
|
|
||||||
{
|
|
||||||
DepsOutputHelper.InstallAndRun(projPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError($"=== POD install not success, exit deps hook...===");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: b2e00c5d9f49480988f78c88fc9d2bac
|
|
||||||
timeCreated: 1702365324
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: d7b67912d7d245e0bf67086a8e2f395a
|
|
||||||
timeCreated: 1702349190
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
GRADLE_VERSION=6.7.1
|
|
||||||
|
|
||||||
export PATH="~/.gradle/${GRADLE_VERSION}:$PATH"
|
|
||||||
export PATH="~/.gradle/${GRADLE_VERSION}/bin:$PATH"
|
|
||||||
export GURU_BIN="~/dev/flutter/guru_app/tools/bin"
|
|
||||||
export PATH="$GURU_BIN:$PATH"
|
|
||||||
|
|
||||||
source .deps_env
|
|
||||||
|
|
||||||
echo "--- BuildName: ${BUILD_NAME}"
|
|
||||||
echo "--- AppName: ${APP_NAME}"
|
|
||||||
echo "--- APP_ID: ${APP_ID}"
|
|
||||||
echo "--- Platform: ${PLATFORM}"
|
|
||||||
echo "--- DIR: ${DIR}"
|
|
||||||
|
|
||||||
|
|
||||||
if [ "${PLATFORM}" = "android" ]; then
|
|
||||||
gradle w
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "----- collect deps start-----"
|
|
||||||
|
|
||||||
depaudit --platform "${PLATFORM}" --dir "${DIR}" --build_version "${BUILD_NAME}" --app_name "${APP_NAME}" --app_id "${APP_ID}" --engine unity
|
|
||||||
|
|
||||||
echo "----- collect deps over -----"
|
|
||||||
|
|
||||||
if [ "${PLATFORM}" = "android" ]; then
|
|
||||||
|
|
||||||
if [ -f ${DIR}/gradlew ]; then
|
|
||||||
${DIR}/gradlew --stop
|
|
||||||
echo "***************** deps collect success! *****************"
|
|
||||||
else
|
|
||||||
echo "***************** gradlew not found, deps collect failed! *****************"
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "***************** deps collect success! *****************"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: d43d22568540f49378be159486b4e1d3
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: bf616a5ad654b418281c06863ad401eb
|
guid: c2bda7db652148e7a73fd2179e22be09
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 1429f4e79961470ba0597d4f3caceee8
|
|
||||||
timeCreated: 1711525393
|
|
||||||
12
README.md
12
README.md
|
|
@ -1,17 +1,5 @@
|
||||||
# Guru SDK Core
|
# Guru SDK Core
|
||||||
|
|
||||||
**Version 2.2.1**
|
|
||||||
|
|
||||||
- 更新 Firebase -> Unity 11.7.0 | FirebaseSDK 10.20.0 (iOS 10.22.0)
|
|
||||||
- 添加 GooglePlay DMA 合规逻辑 (2024 年 3 月 6 日之前升级即可)
|
|
||||||
- 广告渠道 Inmobi 升级 双端版本: Android 10.6.3.0 , iOS 10.6.0.0
|
|
||||||
- 广告渠道 Pubmatic iOS 平台版本指定为 3.2 修复打包报错的问题
|
|
||||||
- 更新内核的 JsonParser 解析器为 JsonConvert
|
|
||||||
- fix: 修复配置导出功能解析报错的问题
|
|
||||||
- fix: 修复 ABTestManager 的解析错误.
|
|
||||||
- fix: 修复 Tch 打点在不满足 0.01 的情况下补偿的逻辑
|
|
||||||
- fix: 修复 GuruConsent 在 iOS 上返回 Purpose 乱码的解析问题, 添加了 TCF 映射规则.
|
|
||||||
|
|
||||||
|
|
||||||
**Version 2.1.0**
|
**Version 2.1.0**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
|
|
||||||
namespace Guru
|
namespace Guru
|
||||||
{
|
{
|
||||||
using System.Linq;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Firebase.RemoteConfig;
|
using Firebase.RemoteConfig;
|
||||||
using Newtonsoft.Json;
|
using Guru.LitJson;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -13,7 +12,7 @@ namespace Guru
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ABTestManager : Singleton<ABTestManager>
|
public class ABTestManager : Singleton<ABTestManager>
|
||||||
{
|
{
|
||||||
public const string Version = "1.0.2";
|
public const string Version = "1.0.0";
|
||||||
private FirebaseRemoteConfig _remoteConfig;
|
private FirebaseRemoteConfig _remoteConfig;
|
||||||
private List<ABParamData> _params;
|
private List<ABParamData> _params;
|
||||||
|
|
||||||
|
|
@ -52,13 +51,10 @@ namespace Guru
|
||||||
|
|
||||||
_remoteConfig = FirebaseRemoteConfig.DefaultInstance;
|
_remoteConfig = FirebaseRemoteConfig.DefaultInstance;
|
||||||
|
|
||||||
Debug.Log($"[AB] --- remoteConfig Counts: {_remoteConfig.Keys.Count()}");
|
|
||||||
|
|
||||||
string strValue;
|
string strValue;
|
||||||
foreach (var key in _remoteConfig.Keys)
|
foreach (var key in _remoteConfig.Keys)
|
||||||
{
|
{
|
||||||
strValue = _remoteConfig.GetValue(key).StringValue;
|
strValue = _remoteConfig.GetValue(key).StringValue;
|
||||||
Debug.Log($"[AB] --- raw config: [{key}] : {strValue}");
|
|
||||||
AddParam(strValue);
|
AddParam(strValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,8 +135,7 @@ namespace Guru
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 发现Guru AB测试标志位
|
// 发现Guru AB测试标志位
|
||||||
// var dict = JsonMapper.ToObject<Dictionary<string, JsonData>>(value);
|
var dict = JsonMapper.ToObject<Dictionary<string, JsonData>>(value);
|
||||||
var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(value);
|
|
||||||
if (null != dict)
|
if (null != dict)
|
||||||
{
|
{
|
||||||
foreach (var k in dict.Keys)
|
foreach (var k in dict.Keys)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
"MaxSdk",
|
"MaxSdk",
|
||||||
"MaxSdk.Scripts",
|
"MaxSdk.Scripts",
|
||||||
"Amazon",
|
"Amazon",
|
||||||
"Amazon.Scripts",
|
|
||||||
"OpenWrapSDK",
|
"OpenWrapSDK",
|
||||||
"UniWebView-CSharp",
|
"UniWebView-CSharp",
|
||||||
"UnityEngine.Purchasing",
|
"UnityEngine.Purchasing",
|
||||||
|
|
@ -18,9 +17,7 @@
|
||||||
"Google.Play.Review",
|
"Google.Play.Review",
|
||||||
"Google.Play.Common",
|
"Google.Play.Common",
|
||||||
"Guru.LitJson",
|
"Guru.LitJson",
|
||||||
"Unity.Advertisement.IosSupport",
|
"Unity.Advertisement.IosSupport"
|
||||||
"Unity.Notifications.Android",
|
|
||||||
"Unity.Notifications.iOS"
|
|
||||||
],
|
],
|
||||||
"includePlatforms": [],
|
"includePlatforms": [],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 2b993d660d0a48b1a098f5d42611b464
|
|
||||||
timeCreated: 1717034031
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 43aa7a523e0141d4a7f64e16e5294d2d
|
|
||||||
timeCreated: 1717034046
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"name": "GuruAdjust.Editor",
|
|
||||||
"includePlatforms": [
|
|
||||||
"Editor"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 82f90ccbb33b42e9ad29f5f5a861dc4a
|
|
||||||
timeCreated: 1717137351
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 522c3aca8edd4e5bb3c57f54460df356
|
|
||||||
timeCreated: 1717137307
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
using System.IO;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Guru
|
|
||||||
{
|
|
||||||
public class AdjustSignatureHelper
|
|
||||||
{
|
|
||||||
|
|
||||||
private static readonly string AndroidLib = "adjust-android-signature-3.13.1.aar";
|
|
||||||
private static readonly string iOSLib = "AdjustSigSdk.a";
|
|
||||||
|
|
||||||
public static void DeployFiles()
|
|
||||||
{
|
|
||||||
var dir = GetParentDir();
|
|
||||||
var files = $"{dir}/Files";
|
|
||||||
if (Directory.Exists(files))
|
|
||||||
{
|
|
||||||
string from, to;
|
|
||||||
bool res;
|
|
||||||
from = $"{files}/{AndroidLib}.f";
|
|
||||||
to = $"{Application.dataPath}/Plugins/Android/{AndroidLib}";
|
|
||||||
res = CopyFile(from, to);
|
|
||||||
if (res) Debug.Log($"Copy <color=#88ff00>{AndroidLib} to {to}</color> success...");
|
|
||||||
from = $"{files}/{AndroidLib}.f.meta";
|
|
||||||
to = $"{Application.dataPath}/Plugins/Android/{AndroidLib}.meta";
|
|
||||||
CopyFile(from, to);
|
|
||||||
|
|
||||||
from = $"{files}/{iOSLib}.f";
|
|
||||||
to = $"{Application.dataPath}/Plugins/iOS/{iOSLib}";
|
|
||||||
res = CopyFile(from, to);
|
|
||||||
if (res) Debug.Log($"Copy <color=#88ff00>{iOSLib} to {to}</color> success...");
|
|
||||||
from = $"{files}/{iOSLib}.f.meta";
|
|
||||||
to = $"{Application.dataPath}/Plugins/iOS/{iOSLib}.meta";
|
|
||||||
CopyFile(from, to);
|
|
||||||
|
|
||||||
AssetDatabase.Refresh();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Log($"<color=red>Files not found: {files}</color>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static string GetParentDir()
|
|
||||||
{
|
|
||||||
var guids = AssetDatabase.FindAssets(nameof(AdjustSignatureHelper));
|
|
||||||
if (guids != null && guids.Length > 0)
|
|
||||||
{
|
|
||||||
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
|
||||||
var dir = Directory.GetParent(Path.GetFullPath(path));
|
|
||||||
return dir.FullName;
|
|
||||||
}
|
|
||||||
return Path.GetFullPath($"{Application.dataPath}/../Packages/com.guru.unity.sdk.core/Runtime/GuruAdjust/Editor/Signature");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool CopyFile(string source, string dest)
|
|
||||||
{
|
|
||||||
if (File.Exists(source))
|
|
||||||
{
|
|
||||||
if (!File.Exists(dest))
|
|
||||||
{
|
|
||||||
File.Delete(dest);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var destDir = Directory.GetParent(dest);
|
|
||||||
if(!destDir.Exists) destDir.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
File.Copy(source, dest, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Log($"<colo=red>File not found: {source}...</color>");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: fcbb67d0a48d4b88bc8fd1430c4bbda4
|
|
||||||
timeCreated: 1717137470
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
namespace Guru
|
|
||||||
{
|
|
||||||
using UnityEditor;
|
|
||||||
|
|
||||||
public class AdjustSignatureMenuItem
|
|
||||||
{
|
|
||||||
[MenuItem("Guru/Adjust/SignatureV3/Deploy Libs")]
|
|
||||||
private static void CopyLibsToPlugins()
|
|
||||||
{
|
|
||||||
AdjustSignatureHelper.DeployFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 5f624e98bef44a399cf808a6aa7f5499
|
|
||||||
timeCreated: 1717137523
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: f0f3f1cbb077474882bf4725c274efa9
|
|
||||||
timeCreated: 1717034059
|
|
||||||
Binary file not shown.
|
|
@ -1,80 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 7dfc92df774d347749c60047aaa3da41
|
|
||||||
PluginImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
iconMap: {}
|
|
||||||
executionOrder: {}
|
|
||||||
defineConstraints: []
|
|
||||||
isPreloaded: 0
|
|
||||||
isOverridable: 0
|
|
||||||
isExplicitlyReferenced: 0
|
|
||||||
validateReferences: 1
|
|
||||||
platformData:
|
|
||||||
- first:
|
|
||||||
: Any
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
Exclude Android: 1
|
|
||||||
Exclude Editor: 1
|
|
||||||
Exclude Linux64: 1
|
|
||||||
Exclude OSXUniversal: 1
|
|
||||||
Exclude Win: 1
|
|
||||||
Exclude Win64: 1
|
|
||||||
Exclude iOS: 0
|
|
||||||
- first:
|
|
||||||
Android: Android
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: ARMv7
|
|
||||||
- first:
|
|
||||||
Any:
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings: {}
|
|
||||||
- first:
|
|
||||||
Editor: Editor
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
DefaultValueInitialized: true
|
|
||||||
OS: AnyOS
|
|
||||||
- first:
|
|
||||||
Standalone: Linux64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: None
|
|
||||||
- first:
|
|
||||||
Standalone: OSXUniversal
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: None
|
|
||||||
- first:
|
|
||||||
Standalone: Win
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: None
|
|
||||||
- first:
|
|
||||||
Standalone: Win64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: None
|
|
||||||
- first:
|
|
||||||
iPhone: iOS
|
|
||||||
second:
|
|
||||||
enabled: 1
|
|
||||||
settings:
|
|
||||||
AddToEmbeddedBinaries: false
|
|
||||||
CPU: AnyCPU
|
|
||||||
CompileFlags:
|
|
||||||
FrameworkDependencies:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
Binary file not shown.
|
|
@ -70,7 +70,7 @@ namespace Guru
|
||||||
{
|
{
|
||||||
string version = UnityEngine.iOS.Device.systemVersion;
|
string version = UnityEngine.iOS.Device.systemVersion;
|
||||||
|
|
||||||
// Debug.Log($"[ATT] --- Get iOS system version: {version}");
|
Debug.Log($"[ATT] --- Get iOS system version: {version}");
|
||||||
|
|
||||||
string tmp = version;
|
string tmp = version;
|
||||||
if (version.Contains(" "))
|
if (version.Contains(" "))
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
-keep class com.amazon.device.ads.** { *; }
|
-keep class com.amazon.device.ads.** { *; }
|
||||||
-keep class com.amazon.aps.** { *; }
|
|
||||||
|
|
@ -79,7 +79,7 @@ namespace Guru
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化平台
|
/// 初始化平台
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Initialize(bool isDebug = false)
|
public void Initialize()
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
Debug.Log($"<color=orange>=== Amazon will not init on Editor ===</color>");
|
Debug.Log($"<color=orange>=== Amazon will not init on Editor ===</color>");
|
||||||
|
|
@ -93,9 +93,11 @@ namespace Guru
|
||||||
// 初始化Amazon
|
// 初始化Amazon
|
||||||
Amazon.Initialize (AmazonAppID);
|
Amazon.Initialize (AmazonAppID);
|
||||||
Amazon.SetAdNetworkInfo(new AdNetworkInfo(DTBAdNetwork.MAX));
|
Amazon.SetAdNetworkInfo(new AdNetworkInfo(DTBAdNetwork.MAX));
|
||||||
Debug.Log($"[Ads] --- Amazon init start isDebug:{isDebug}, AmazonID:{AmazonAppID}");
|
#if UNITY_EDITOR || DEBUG
|
||||||
Amazon.EnableTesting (isDebug); // Make sure to take this off when going live.
|
Amazon.EnableTesting (true); // Make sure to take this off when going live.
|
||||||
Amazon.EnableLogging (isDebug);
|
#else
|
||||||
|
Amazon.EnableLogging (false);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if UNITY_IOS
|
#if UNITY_IOS
|
||||||
Amazon.SetAPSPublisherExtendedIdFeatureEnabled(true);
|
Amazon.SetAPSPublisherExtendedIdFeatureEnabled(true);
|
||||||
|
|
|
||||||
|
|
@ -50,22 +50,16 @@ namespace Guru
|
||||||
* before it can request an ad using OpenWrap SDK.
|
* before it can request an ad using OpenWrap SDK.
|
||||||
* The storeURL is the URL where users can download your app from the App Store/Google Play Store.
|
* The storeURL is the URL where users can download your app from the App Store/Google Play Store.
|
||||||
*/
|
*/
|
||||||
public void Initialize(bool isDebug = false)
|
public void Initialize()
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
Debug.Log($"<color=orange>=== PubMatic will not init on Editor ===</color>");
|
Debug.Log($"<color=orange>=== PubMatic will not init on Editor ===</color>");
|
||||||
#endif
|
#endif
|
||||||
if (!IsEnabled)
|
if (!IsEnabled)
|
||||||
{
|
{
|
||||||
Debug.Log($"[Ads] --- PubMatic is not enabled");
|
Debug.Log($"[Ads] --- PubMatic is not enabled");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(PMStoreUrl))
|
|
||||||
{
|
|
||||||
Debug.Log($"[Ads] --- PubMatic with empty store url. skip initialize...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var appInfo = new POBApplicationInfo();
|
var appInfo = new POBApplicationInfo();
|
||||||
appInfo.StoreURL = new Uri(PMStoreUrl);
|
appInfo.StoreURL = new Uri(PMStoreUrl);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 2b371a8d213e4b7db1d5d5348581eec3
|
|
||||||
timeCreated: 1709268083
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 0b106e716338488090bac0280df955cd
|
|
||||||
timeCreated: 1709268091
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
namespace Guru
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tradplus 广告配置
|
|
||||||
/// </summary>
|
|
||||||
public partial class GuruSettings
|
|
||||||
{
|
|
||||||
[Header("Tradplus 广告配置")]
|
|
||||||
public AdChannelSettings TradplusSetting;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 284881a6a8164dbda8a288a73a5c4933
|
|
||||||
timeCreated: 1709268103
|
|
||||||
|
|
@ -20,11 +20,16 @@ Sample Dependencies.xml:
|
||||||
<androidPackage spec="com.squareup.retrofit2:converter-gson:2.7.1" />
|
<androidPackage spec="com.squareup.retrofit2:converter-gson:2.7.1" />
|
||||||
<androidPackage spec="com.squareup.retrofit2:adapter-rxjava2:2.7.1" />
|
<androidPackage spec="com.squareup.retrofit2:adapter-rxjava2:2.7.1" />
|
||||||
<androidPackage spec="com.squareup.okhttp3:okhttp:4.9.3" />
|
<androidPackage spec="com.squareup.okhttp3:okhttp:4.9.3" />
|
||||||
|
|
||||||
<!-- <androidPackage spec="com.mapzen:on-the-road:0.8.1" />-->
|
<!-- <androidPackage spec="com.mapzen:on-the-road:0.8.1" />-->
|
||||||
<!-- <androidPackage spec="com.squareup.retrofit2:retrofit:2.7.1" />-->
|
<!-- <androidPackage spec="com.squareup.retrofit2:retrofit:2.7.1" />-->
|
||||||
</androidPackages>
|
</androidPackages>
|
||||||
<iosPods>
|
<iosPods>
|
||||||
<iosPod name="GuruAnalyticsLib" bitcodeEnabled="false" path="Packages/com.guru.unity.sdk.core/Runtime/GuruAnalytics/Plugins/iOS" />
|
<iosPod name="GuruAnalyticsLib" version="0.3.2" bitcodeEnabled="false">
|
||||||
|
<sources>
|
||||||
|
<source>git@github.com:castbox/GuruSpecs.git</source>
|
||||||
|
</sources>
|
||||||
|
</iosPod>
|
||||||
<iosPod name="JJException" bitcodeEnabled="false" />
|
<iosPod name="JJException" bitcodeEnabled="false" />
|
||||||
</iosPods>
|
</iosPods>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 66c5f430ab9654ef4a2376e71aa04bca
|
guid: a336b814594434b4092d38e5ce76577a
|
||||||
PluginImporter:
|
PluginImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 3bec370c02c6448298433a674a4a6b2b
|
guid: c5a9f9e11213b4bb78856debe4c967ca
|
||||||
PluginImporter:
|
PluginImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: e53e2bfca0fd949559d383674081f737
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 502f707bde2a24fadb6ec09ac5a3593f
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPITypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>CA92.1</string>
|
|
||||||
</array>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>C617.1</string>
|
|
||||||
</array>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>35F9.1</string>
|
|
||||||
<string>8FFB.1</string>
|
|
||||||
<string>3D61.1</string>
|
|
||||||
</array>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>NSPrivacyTracking</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSPrivacyTrackingDomains</key>
|
|
||||||
<array>
|
|
||||||
<string></string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 63885139be48c43ae8cd3b1c403a686f
|
|
||||||
PluginImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
iconMap: {}
|
|
||||||
executionOrder: {}
|
|
||||||
defineConstraints: []
|
|
||||||
isPreloaded: 0
|
|
||||||
isOverridable: 0
|
|
||||||
isExplicitlyReferenced: 0
|
|
||||||
validateReferences: 1
|
|
||||||
platformData:
|
|
||||||
- first:
|
|
||||||
: Any
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
Exclude Android: 1
|
|
||||||
Exclude Editor: 1
|
|
||||||
Exclude Linux64: 1
|
|
||||||
Exclude OSXUniversal: 1
|
|
||||||
Exclude Win: 1
|
|
||||||
Exclude Win64: 1
|
|
||||||
Exclude iOS: 1
|
|
||||||
- first:
|
|
||||||
Android: Android
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: ARMv7
|
|
||||||
- first:
|
|
||||||
Any:
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings: {}
|
|
||||||
- first:
|
|
||||||
Editor: Editor
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
DefaultValueInitialized: true
|
|
||||||
OS: AnyOS
|
|
||||||
- first:
|
|
||||||
Standalone: Linux64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: OSXUniversal
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: Win
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: Win64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
iPhone: iOS
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
AddToEmbeddedBinaries: false
|
|
||||||
CPU: AnyCPU
|
|
||||||
CompileFlags:
|
|
||||||
FrameworkDependencies:
|
|
||||||
- first:
|
|
||||||
tvOS: tvOS
|
|
||||||
second:
|
|
||||||
enabled: 1
|
|
||||||
settings: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: d30316515c87a4421bc7032194f888e1
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: e0086576c1ac64707b788bef25dc9316
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
//
|
|
||||||
// GuruAnalytics.swift
|
|
||||||
// GuruAnalytics_iOS
|
|
||||||
//
|
|
||||||
// Created by mayue on 2022/11/4.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public class GuruAnalytics: NSObject {
|
|
||||||
|
|
||||||
internal static var uploadPeriodInSecond: Double = 60
|
|
||||||
internal static var batchLimit: Int = 25
|
|
||||||
internal static var eventExpiredSeconds: Double = 7 * 24 * 60 * 60
|
|
||||||
internal static var initializeTimeout: Double = 5
|
|
||||||
internal static var saasXAPPID = ""
|
|
||||||
internal static var saasXDEVICEINFO = ""
|
|
||||||
internal static var loggerDebug = true
|
|
||||||
internal static var enableUpload = true
|
|
||||||
|
|
||||||
/// 初始化设置
|
|
||||||
/// - Parameters:
|
|
||||||
/// - uploadPeriodInSecond: 批量上传周期,单位秒
|
|
||||||
/// - batchLimit: 批量条数
|
|
||||||
/// - eventExpiredSeconds: 数据过期时间,单位秒
|
|
||||||
/// - initializeTimeout: 初始化后等待user id/device id/firebase pseudo id等属性超时时间,单位秒
|
|
||||||
/// - saasXAPPID: 中台接口header中的X-APP-ID
|
|
||||||
/// - saasXDEVICEINFO: 中台接口header中的X-DEVICE-INFO
|
|
||||||
/// - loggerDebug: 开启控制台输出debug信息
|
|
||||||
@objc
|
|
||||||
public class func initializeLib(uploadPeriodInSecond: Double = 60,
|
|
||||||
batchLimit: Int = 25,
|
|
||||||
eventExpiredSeconds: Double = 7 * 24 * 60 * 60,
|
|
||||||
initializeTimeout: Double = 5,
|
|
||||||
saasXAPPID: String,
|
|
||||||
saasXDEVICEINFO: String,
|
|
||||||
loggerDebug: Bool = true) {
|
|
||||||
Self.uploadPeriodInSecond = uploadPeriodInSecond
|
|
||||||
Self.batchLimit = batchLimit
|
|
||||||
Self.eventExpiredSeconds = eventExpiredSeconds
|
|
||||||
Self.initializeTimeout = initializeTimeout
|
|
||||||
Self.saasXAPPID = saasXAPPID
|
|
||||||
Self.saasXDEVICEINFO = saasXDEVICEINFO
|
|
||||||
Self.loggerDebug = loggerDebug
|
|
||||||
_ = Manager.shared
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 记录event
|
|
||||||
@objc
|
|
||||||
public class func logEvent(_ name: String, parameters: [String : Any]?) {
|
|
||||||
Manager.shared.logEvent(name, parameters: parameters)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 中台ID。只在未获取到uid时可以为空
|
|
||||||
@objc
|
|
||||||
public class func setUserID(_ userID: String?) {
|
|
||||||
setUserProperty(userID, forName: .uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 设备ID(用户的设备ID,iOS取用户的IDFV或UUID,Android取androidID)
|
|
||||||
@objc
|
|
||||||
public class func setDeviceId(_ deviceId: String?) {
|
|
||||||
setUserProperty(deviceId, forName: .deviceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// adjust_id。只在未获取到adjust时可以为空
|
|
||||||
@objc
|
|
||||||
public class func setAdjustId(_ adjustId: String?) {
|
|
||||||
setUserProperty(adjustId, forName: .adjustId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 广告 ID/广告标识符 (IDFA)
|
|
||||||
@objc
|
|
||||||
public class func setAdId(_ adId: String?) {
|
|
||||||
setUserProperty(adId, forName: .adId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 用户的pseudo_id
|
|
||||||
@objc
|
|
||||||
public class func setFirebaseId(_ firebaseId: String?) {
|
|
||||||
setUserProperty(firebaseId, forName: .firebaseId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// screen name
|
|
||||||
@objc
|
|
||||||
public class func setScreen(_ name: String) {
|
|
||||||
Manager.shared.setScreen(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 设置userproperty
|
|
||||||
@objc
|
|
||||||
public class func setUserProperty(_ value: String?, forName name: String) {
|
|
||||||
Manager.shared.setUserProperty(value ?? "", forName: name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 移除userproperty
|
|
||||||
@objc
|
|
||||||
public class func removeUserProperties(forNames names: [String]) {
|
|
||||||
Manager.shared.removeUserProperties(forNames: names)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取events相关日志文件zip包
|
|
||||||
/// zip解压密码:Castbox123
|
|
||||||
@available(*, deprecated, renamed: "eventsLogsDirectory", message: "废弃,使用eventsLogsDirectory方法获取日志文件目录URL")
|
|
||||||
@objc
|
|
||||||
public class func eventsLogsArchive(_ callback: @escaping (_ url: URL?) -> Void) {
|
|
||||||
Manager.shared.eventsLogsArchive(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取events相关日志文件目录
|
|
||||||
@objc
|
|
||||||
public class func eventsLogsDirectory(_ callback: @escaping (_ url: URL?) -> Void) {
|
|
||||||
Manager.shared.eventsLogsDirURL(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 更新events上报服务器域名
|
|
||||||
/// host: 服务器域名,例如:“abc.bbb.com”, "https://abc.bbb.com", "http://abc.bbb.com"
|
|
||||||
@objc
|
|
||||||
public class func setEventsUploadEndPoint(host: String?) {
|
|
||||||
UserDefaults.eventsServerHost = host
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取events统计数据
|
|
||||||
/// - Parameter callback: 数据回调
|
|
||||||
/// - callback parameters:
|
|
||||||
/// - uploadedEventsCount: 上传后端成功event条数
|
|
||||||
/// - loggedEventsCount: 已记录event总条数
|
|
||||||
@objc
|
|
||||||
@available(*, deprecated, message: "used for debug, will be removed on any future released versions")
|
|
||||||
public class func debug_eventsStatistics(_ callback: @escaping (_ uploadedEventsCount: Int, _ loggedEventsCount: Int) -> Void) {
|
|
||||||
Manager.shared.debug_eventsStatistics(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 将内部事件信息上报给应用层
|
|
||||||
/// - Parameter reportCallback: 数据回调
|
|
||||||
/// - callback parameters:
|
|
||||||
/// - eventCode: 事件代码
|
|
||||||
/// - info: 事件相关信息
|
|
||||||
@objc
|
|
||||||
public class func registerInternalEventObserver(reportCallback: @escaping (_ eventCode: Int, _ info: String) -> Void) {
|
|
||||||
Manager.shared.registerInternalEventObserver(reportCallback: reportCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取当前user property
|
|
||||||
@objc
|
|
||||||
public class func getUserProperties() -> [String : String] {
|
|
||||||
return Manager.shared.getUserProperties()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 设置上传开关,默认为true
|
|
||||||
/// true - 开启上传
|
|
||||||
/// false - 关闭上传
|
|
||||||
@objc
|
|
||||||
public class func setEnableUpload(isOn: Bool = true) -> Void {
|
|
||||||
enableUpload = isOn
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 669b744f21d994fd3b6fb7aeb95b0669
|
|
||||||
PluginImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
iconMap: {}
|
|
||||||
executionOrder: {}
|
|
||||||
defineConstraints: []
|
|
||||||
isPreloaded: 0
|
|
||||||
isOverridable: 0
|
|
||||||
isExplicitlyReferenced: 0
|
|
||||||
validateReferences: 1
|
|
||||||
platformData:
|
|
||||||
- first:
|
|
||||||
: Any
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
Exclude Android: 1
|
|
||||||
Exclude Editor: 1
|
|
||||||
Exclude Linux64: 1
|
|
||||||
Exclude OSXUniversal: 1
|
|
||||||
Exclude Win: 1
|
|
||||||
Exclude Win64: 1
|
|
||||||
Exclude iOS: 1
|
|
||||||
- first:
|
|
||||||
Android: Android
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: ARMv7
|
|
||||||
- first:
|
|
||||||
Any:
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings: {}
|
|
||||||
- first:
|
|
||||||
Editor: Editor
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
DefaultValueInitialized: true
|
|
||||||
OS: AnyOS
|
|
||||||
- first:
|
|
||||||
Standalone: Linux64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: OSXUniversal
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: Win
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: Win64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
iPhone: iOS
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
AddToEmbeddedBinaries: false
|
|
||||||
CPU: AnyCPU
|
|
||||||
CompileFlags:
|
|
||||||
FrameworkDependencies:
|
|
||||||
- first:
|
|
||||||
tvOS: tvOS
|
|
||||||
second:
|
|
||||||
enabled: 1
|
|
||||||
settings: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: c6cbae57da78c46c7918b2bfd24d7335
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: a9b9cc55c438041a7ae3ce46bd896d8d
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,228 +0,0 @@
|
||||||
//
|
|
||||||
// DBEntities.swift
|
|
||||||
// Alamofire
|
|
||||||
//
|
|
||||||
// Created by mayue on 2022/11/4.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CryptoSwift
|
|
||||||
|
|
||||||
internal enum Entity {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Entity {
|
|
||||||
struct EventRecord: Codable {
|
|
||||||
|
|
||||||
enum Priority: Int, Codable {
|
|
||||||
case EMERGENCE = 0
|
|
||||||
case HIGH = 5
|
|
||||||
case DEFAULT = 10
|
|
||||||
case LOW = 15
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TransitionStatus: Int, Codable {
|
|
||||||
case idle = 0
|
|
||||||
case instransition = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
let recordId: String
|
|
||||||
let eventName: String
|
|
||||||
let eventJson: String
|
|
||||||
///单位毫秒
|
|
||||||
let timestamp: Int64
|
|
||||||
let priority: Priority
|
|
||||||
let transitionStatus: TransitionStatus
|
|
||||||
|
|
||||||
init(eventName: String, event: Entity.Event, priority: Priority = .DEFAULT, transitionStatus: TransitionStatus = .idle) {
|
|
||||||
let now = Date()
|
|
||||||
let eventJson = event.asString ?? ""
|
|
||||||
if eventJson.isEmpty {
|
|
||||||
cdPrint("[WARNING] error for convert event to json")
|
|
||||||
}
|
|
||||||
self.recordId = "\(eventName)\(eventJson)\(now.timeIntervalSince1970)\(Int.random(in: Int.min...Int.max))".md5()
|
|
||||||
self.eventName = eventName
|
|
||||||
self.eventJson = eventJson
|
|
||||||
self.timestamp = event.timestamp
|
|
||||||
self.priority = priority
|
|
||||||
self.transitionStatus = transitionStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
init(recordId: String, eventName: String, eventJson: String, timestamp: Int64, priority: Int, transitionStatus: Int) {
|
|
||||||
self.recordId = recordId
|
|
||||||
self.eventName = eventName
|
|
||||||
self.eventJson = eventJson
|
|
||||||
self.timestamp = timestamp
|
|
||||||
self.priority = .init(rawValue: priority) ?? .DEFAULT
|
|
||||||
self.transitionStatus = .init(rawValue: transitionStatus) ?? .idle
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case recordId
|
|
||||||
case eventName
|
|
||||||
case eventJson
|
|
||||||
case timestamp
|
|
||||||
case priority
|
|
||||||
case transitionStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
static func createTableSql(with name: String) -> String {
|
|
||||||
return """
|
|
||||||
CREATE TABLE IF NOT EXISTS \(name)(
|
|
||||||
\(CodingKeys.recordId.rawValue) TEXT UNIQUE NOT NULL PRIMARY KEY,
|
|
||||||
\(CodingKeys.eventName.rawValue) TEXT NOT NULL,
|
|
||||||
\(CodingKeys.eventJson.rawValue) TEXT NOT NULL,
|
|
||||||
\(CodingKeys.timestamp.rawValue) INTEGER,
|
|
||||||
\(CodingKeys.priority.rawValue) INTEGER,
|
|
||||||
\(CodingKeys.transitionStatus.rawValue) INTEGER);
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertSql(to tableName: String) -> String {
|
|
||||||
return "INSERT INTO \(tableName) VALUES ('\(recordId)', '\(eventName)', '\(eventJson)', '\(timestamp)', '\(priority.rawValue)', '\(transitionStatus.rawValue)')"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Entity {
|
|
||||||
struct Event: Codable {
|
|
||||||
///客户端中记录此事件的时间(采用世界协调时间,毫秒为单位)
|
|
||||||
let timestamp: Int64
|
|
||||||
let event: String
|
|
||||||
let userInfo: UserInfo
|
|
||||||
let param: [String: EventValue]
|
|
||||||
let properties: [String: String]
|
|
||||||
let eventId: String
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case timestamp
|
|
||||||
case userInfo = "info"
|
|
||||||
case event, param, properties
|
|
||||||
case eventId
|
|
||||||
}
|
|
||||||
|
|
||||||
init(timestamp: Int64, event: String, userInfo: UserInfo, parameters: [String : Any], properties: [String : String]) throws {
|
|
||||||
guard let normalizedEvent = Self.normalizeKey(event),
|
|
||||||
normalizedEvent == event else {
|
|
||||||
cdPrint("drop event because of illegal event name: \(event)")
|
|
||||||
cdPrint("standard: https://developers.google.com/android/reference/com/google/firebase/analytics/FirebaseAnalytics.Event")
|
|
||||||
throw NSError(domain: "cunstrcting event error", code: 0, userInfo: [NSLocalizedDescriptionKey : "illegal event name: \(event)"])
|
|
||||||
}
|
|
||||||
self.eventId = UUID().uuidString.lowercased()
|
|
||||||
self.timestamp = timestamp
|
|
||||||
self.event = normalizedEvent
|
|
||||||
self.userInfo = userInfo
|
|
||||||
self.param = Self.normalizeParameters(parameters)
|
|
||||||
self.properties = properties
|
|
||||||
}
|
|
||||||
|
|
||||||
static let maxParametersCount = 25
|
|
||||||
static let maxKeyLength = 40
|
|
||||||
static let maxParameterStringValueLength = 128
|
|
||||||
|
|
||||||
static func normalizeParameters(_ parameters: [String : Any]) -> [String : EventValue] {
|
|
||||||
var params = [String : EventValue]()
|
|
||||||
var count = 0
|
|
||||||
parameters.sorted(by: { $0.key < $1.key }).forEach({ key, value in
|
|
||||||
|
|
||||||
guard count < maxParametersCount else {
|
|
||||||
cdPrint("too many parameters")
|
|
||||||
cdPrint("standard: https://developers.google.com/android/reference/com/google/firebase/analytics/FirebaseAnalytics.Event")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let normalizedKey = normalizeKey(key),
|
|
||||||
normalizedKey == key else {
|
|
||||||
cdPrint("drop event parameter because of illegal key: \(key)")
|
|
||||||
cdPrint("standard: https://developers.google.com/android/reference/com/google/firebase/analytics/FirebaseAnalytics.Event")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let value = value as? String {
|
|
||||||
params[normalizedKey] = Entity.EventValue(stringValue: String(value.prefix(maxParameterStringValueLength)))
|
|
||||||
} else if let value = value as? Int {
|
|
||||||
params[normalizedKey] = Entity.EventValue(longValue: Int64(value))
|
|
||||||
} else if let value = value as? Int64 {
|
|
||||||
params[normalizedKey] = Entity.EventValue(longValue: value)
|
|
||||||
} else if let value = value as? Double {
|
|
||||||
params[normalizedKey] = Entity.EventValue(doubleValue: value)
|
|
||||||
} else {
|
|
||||||
params[normalizedKey] = Entity.EventValue(stringValue: String("\(value)".prefix(maxParameterStringValueLength)))
|
|
||||||
}
|
|
||||||
|
|
||||||
count += 1
|
|
||||||
})
|
|
||||||
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
static func normalizeKey(_ key: String) -> String? {
|
|
||||||
var mutableKey = key
|
|
||||||
|
|
||||||
while let first = mutableKey.first,
|
|
||||||
!first.isLetter {
|
|
||||||
_ = mutableKey.removeFirst()
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalizedKey = ""
|
|
||||||
var count = 0
|
|
||||||
mutableKey.forEach { c in
|
|
||||||
guard count < maxKeyLength,
|
|
||||||
c.isAlphabetic || c.isDigit || c == "_" else { return }
|
|
||||||
normalizedKey.append(c)
|
|
||||||
count += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizedKey.isEmpty ? nil : normalizedKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///用户信息
|
|
||||||
struct UserInfo: Codable {
|
|
||||||
///中台ID。只在未获取到uid时可以为空
|
|
||||||
let uid: String?
|
|
||||||
///设备ID(用户的设备ID,iOS取用户的IDFV或UUID,Android取androidID)
|
|
||||||
let deviceId: String?
|
|
||||||
///adjust_id。只在未获取到adjust时可以为空
|
|
||||||
let adjustId: String?
|
|
||||||
///广告 ID/广告标识符 (IDFA)
|
|
||||||
let adId: String?
|
|
||||||
///用户的pseudo_id
|
|
||||||
let firebaseId: String?
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case deviceId
|
|
||||||
case uid
|
|
||||||
case adjustId
|
|
||||||
case adId
|
|
||||||
case firebaseId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 参数对应的值
|
|
||||||
struct EventValue: Codable {
|
|
||||||
let stringValue: String? // 事件参数的字符串值
|
|
||||||
let longValue: Int64? // 事件参数的整数值
|
|
||||||
let doubleValue: Double? // 事件参数的小数值。注意:APP序列化成JSON时,注意不要序列化成科学计数法
|
|
||||||
|
|
||||||
init(stringValue: String? = nil, longValue: Int64? = nil, doubleValue: Double? = nil) {
|
|
||||||
self.stringValue = stringValue
|
|
||||||
self.longValue = longValue
|
|
||||||
self.doubleValue = doubleValue
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case stringValue = "s"
|
|
||||||
case longValue = "i"
|
|
||||||
case doubleValue = "d"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Entity {
|
|
||||||
struct SystemTimeResult: Codable {
|
|
||||||
let data: Int64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: f63b0ff90afd0409781c1266dc618875
|
|
||||||
PluginImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
iconMap: {}
|
|
||||||
executionOrder: {}
|
|
||||||
defineConstraints: []
|
|
||||||
isPreloaded: 0
|
|
||||||
isOverridable: 0
|
|
||||||
isExplicitlyReferenced: 0
|
|
||||||
validateReferences: 1
|
|
||||||
platformData:
|
|
||||||
- first:
|
|
||||||
: Any
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
Exclude Android: 1
|
|
||||||
Exclude Editor: 1
|
|
||||||
Exclude Linux64: 1
|
|
||||||
Exclude OSXUniversal: 1
|
|
||||||
Exclude Win: 1
|
|
||||||
Exclude Win64: 1
|
|
||||||
Exclude iOS: 1
|
|
||||||
- first:
|
|
||||||
Android: Android
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: ARMv7
|
|
||||||
- first:
|
|
||||||
Any:
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings: {}
|
|
||||||
- first:
|
|
||||||
Editor: Editor
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
DefaultValueInitialized: true
|
|
||||||
OS: AnyOS
|
|
||||||
- first:
|
|
||||||
Standalone: Linux64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: OSXUniversal
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: Win
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: Win64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
iPhone: iOS
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
AddToEmbeddedBinaries: false
|
|
||||||
CPU: AnyCPU
|
|
||||||
CompileFlags:
|
|
||||||
FrameworkDependencies:
|
|
||||||
- first:
|
|
||||||
tvOS: tvOS
|
|
||||||
second:
|
|
||||||
enabled: 1
|
|
||||||
settings: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: bb7dde11f0ad6496ca231330136b7b61
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,391 +0,0 @@
|
||||||
//
|
|
||||||
// Database.swift
|
|
||||||
// GuruAnalytics_iOS
|
|
||||||
//
|
|
||||||
// Created by mayue on 2022/11/4.
|
|
||||||
// Copyright © 2022 Guru Network Limited. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import FMDB
|
|
||||||
|
|
||||||
internal class Database {
|
|
||||||
|
|
||||||
typealias PropertyName = GuruAnalytics.PropertyName
|
|
||||||
|
|
||||||
enum TableName: String, CaseIterable {
|
|
||||||
case event = "event"
|
|
||||||
}
|
|
||||||
|
|
||||||
private let dbIOQueue = DispatchQueue.init(label: "com.guru.analytics.db.io.queue", qos: .userInitiated)
|
|
||||||
|
|
||||||
private let dbQueueRelay = BehaviorRelay<FMDatabaseQueue?>(value: nil)
|
|
||||||
private let bag = DisposeBag()
|
|
||||||
|
|
||||||
/// 更新数据库表结构后,需要更新数据库版本
|
|
||||||
private let currentDBVersion = DBVersionHistory.v_3
|
|
||||||
|
|
||||||
private var dbVersion: Database.DBVersionHistory {
|
|
||||||
get {
|
|
||||||
if let v = UserDefaults.defaults?.value(forKey: UserDefaults.dbVersionKey) as? String,
|
|
||||||
let dbV = Database.DBVersionHistory.init(rawValue: v) {
|
|
||||||
return dbV
|
|
||||||
} else {
|
|
||||||
return .initialVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
UserDefaults.defaults?.set(newValue.rawValue, forKey: UserDefaults.dbVersionKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal init() {
|
|
||||||
|
|
||||||
dbIOQueue.async { [weak self] in
|
|
||||||
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
|
|
||||||
let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory,
|
|
||||||
.userDomainMask,
|
|
||||||
true).last! + "/GuruAnalytics"
|
|
||||||
|
|
||||||
if !FileManager.default.fileExists(atPath: applicationSupportPath) {
|
|
||||||
do {
|
|
||||||
try FileManager.default.createDirectory(atPath: applicationSupportPath, withIntermediateDirectories: true)
|
|
||||||
} catch {
|
|
||||||
assertionFailure("create db path error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let dbPath = applicationSupportPath + "/analytics.db"
|
|
||||||
let queue = FMDatabaseQueue(url: URL(fileURLWithPath: dbPath))!
|
|
||||||
cdPrint("database path: \(queue.path ?? "")")
|
|
||||||
|
|
||||||
self.createEventTable(in: queue)
|
|
||||||
.filter { $0 }
|
|
||||||
.flatMap { _ in
|
|
||||||
self.migrateDB(in: queue).asMaybe()
|
|
||||||
}
|
|
||||||
.flatMap({ _ in
|
|
||||||
self.resetAllTransitionStatus(in: queue).asMaybe()
|
|
||||||
})
|
|
||||||
.subscribe(onSuccess: { _ in
|
|
||||||
self.dbQueueRelay.accept(queue)
|
|
||||||
})
|
|
||||||
.disposed(by: self.bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal extension Database {
|
|
||||||
|
|
||||||
func addEventRecords(_ events: Entity.EventRecord) -> Single<Void> {
|
|
||||||
cdPrint(#function)
|
|
||||||
return mapTransactionToSingle { (db) in
|
|
||||||
try db.executeUpdate(events.insertSql(to: TableName.event.rawValue), values: nil)
|
|
||||||
}
|
|
||||||
.do(onSuccess: { [weak self] (_) in
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchEventRecordsToUpload(limit: Int) -> Single<[Entity.EventRecord]> {
|
|
||||||
return mapTransactionToSingle { (db) in
|
|
||||||
let querySQL: String =
|
|
||||||
"""
|
|
||||||
SELECT * FROM \(TableName.event.rawValue)
|
|
||||||
WHERE \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) IS NULL
|
|
||||||
OR \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) != \(Entity.EventRecord.TransitionStatus.instransition.rawValue)
|
|
||||||
ORDER BY \(Entity.EventRecord.CodingKeys.priority.rawValue) ASC, \(Entity.EventRecord.CodingKeys.timestamp.rawValue) ASC
|
|
||||||
LIMIT \(limit)
|
|
||||||
"""
|
|
||||||
cdPrint(#function + "query sql: \(querySQL)")
|
|
||||||
let results = try db.executeQuery(querySQL, values: nil) //[ASC | DESC]
|
|
||||||
var t: [Entity.EventRecord] = []
|
|
||||||
while results.next() {
|
|
||||||
guard let recordId = results.string(forColumnIndex: 0),
|
|
||||||
let eventName = results.string(forColumnIndex: 1),
|
|
||||||
let eventJson = results.string(forColumnIndex: 2) else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
let priority: Int = results.columnIsNull(Entity.EventRecord.CodingKeys.priority.rawValue) ?
|
|
||||||
Entity.EventRecord.Priority.DEFAULT.rawValue : Int(results.int(forColumn: Entity.EventRecord.CodingKeys.priority.rawValue))
|
|
||||||
|
|
||||||
let ts: Int = results.columnIsNull(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) ?
|
|
||||||
Entity.EventRecord.TransitionStatus.idle.rawValue : Int(results.int(forColumn: Entity.EventRecord.CodingKeys.transitionStatus.rawValue))
|
|
||||||
|
|
||||||
let record = Entity.EventRecord(recordId: recordId, eventName: eventName, eventJson: eventJson,
|
|
||||||
timestamp: results.longLongInt(forColumn: Entity.EventRecord.CodingKeys.timestamp.rawValue),
|
|
||||||
priority: priority, transitionStatus: ts)
|
|
||||||
t.append(record)
|
|
||||||
}
|
|
||||||
|
|
||||||
results.close()
|
|
||||||
|
|
||||||
try t.forEach { record in
|
|
||||||
let updateSQL =
|
|
||||||
"""
|
|
||||||
UPDATE \(TableName.event.rawValue)
|
|
||||||
SET \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) = \(Entity.EventRecord.TransitionStatus.instransition.rawValue)
|
|
||||||
WHERE \(Entity.EventRecord.CodingKeys.recordId.rawValue) = '\(record.recordId)'
|
|
||||||
"""
|
|
||||||
try db.executeUpdate(updateSQL, values: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteEventRecords(_ recordIds: [String]) -> Single<Void> {
|
|
||||||
guard !recordIds.isEmpty else {
|
|
||||||
return .just(())
|
|
||||||
}
|
|
||||||
cdPrint(#function + "\(recordIds)")
|
|
||||||
return mapTransactionToSingle { db in
|
|
||||||
try recordIds.forEach { item in
|
|
||||||
try db.executeUpdate("DELETE FROM \(TableName.event.rawValue) WHERE \(Entity.EventRecord.CodingKeys.recordId.rawValue) = '\(item)'", values: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.do(onSuccess: { [weak self] (_) in
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
||||||
}, onError: { error in
|
|
||||||
cdPrint("\(#function) error: \(error)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeOutdatedEventRecords(earlierThan: Int64) -> Single<Void> {
|
|
||||||
return mapTransactionToSingle { db in
|
|
||||||
let sql = """
|
|
||||||
DELETE FROM \(TableName.event.rawValue)
|
|
||||||
WHERE \(Entity.EventRecord.CodingKeys.timestamp.rawValue) < \(earlierThan)
|
|
||||||
"""
|
|
||||||
try db.executeUpdate(sql, values: nil)
|
|
||||||
}
|
|
||||||
.do(onSuccess: { [weak self] (_) in
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
||||||
}, onError: { error in
|
|
||||||
cdPrint("\(#function) error: \(error)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetTransitionStatus(for recordIds: [String]) -> Single<Void> {
|
|
||||||
guard !recordIds.isEmpty else {
|
|
||||||
return .just(())
|
|
||||||
}
|
|
||||||
cdPrint(#function + "\(recordIds)")
|
|
||||||
return mapTransactionToSingle { db in
|
|
||||||
try recordIds.forEach { item in
|
|
||||||
let updateSQL =
|
|
||||||
"""
|
|
||||||
UPDATE \(TableName.event.rawValue)
|
|
||||||
SET \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) = \(Entity.EventRecord.TransitionStatus.idle.rawValue)
|
|
||||||
WHERE \(Entity.EventRecord.CodingKeys.recordId.rawValue) = '\(item)'
|
|
||||||
"""
|
|
||||||
try db.executeUpdate(updateSQL, values: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.do(onSuccess: { [weak self] (_) in
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
||||||
}, onError: { error in
|
|
||||||
cdPrint("\(#function) error: \(error)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func uploadableEventRecordCount() -> Single<Int> {
|
|
||||||
return mapTransactionToSingle { db in
|
|
||||||
let querySQL =
|
|
||||||
"""
|
|
||||||
SELECT count(*) as Count FROM \(TableName.event.rawValue)
|
|
||||||
WHERE \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) IS NULL
|
|
||||||
OR \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) != \(Entity.EventRecord.TransitionStatus.instransition.rawValue)
|
|
||||||
"""
|
|
||||||
let result = try db.executeQuery(querySQL, values: nil)
|
|
||||||
var count = 0
|
|
||||||
while result.next() {
|
|
||||||
count = Int(result.int(forColumn: "Count"))
|
|
||||||
}
|
|
||||||
result.parentDB = nil
|
|
||||||
result.close()
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uploadableEventRecordCountOb() -> Observable<Int> {
|
|
||||||
return NotificationCenter.default.rx.notification(tableUpdateNotification(TableName.event.rawValue))
|
|
||||||
.startWith(Notification(name: tableUpdateNotification(TableName.event.rawValue)))
|
|
||||||
.flatMap({ [weak self] (_) -> Observable<Int> in
|
|
||||||
guard let `self` = self else {
|
|
||||||
return Observable.empty()
|
|
||||||
}
|
|
||||||
return self.uploadableEventRecordCount().asObservable()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasFgEventRecord() -> Single<Bool> {
|
|
||||||
return mapTransactionToSingle { db in
|
|
||||||
let querySQL =
|
|
||||||
"""
|
|
||||||
SELECT count(*) as Count FROM \(TableName.event.rawValue)
|
|
||||||
WHERE \(Entity.EventRecord.CodingKeys.eventName.rawValue) == '\(GuruAnalytics.fgEvent.name)'
|
|
||||||
"""
|
|
||||||
let result = try db.executeQuery(querySQL, values: nil)
|
|
||||||
var count = 0
|
|
||||||
while result.next() {
|
|
||||||
count = Int(result.int(forColumn: "Count"))
|
|
||||||
}
|
|
||||||
result.parentDB = nil
|
|
||||||
result.close()
|
|
||||||
return count > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension Database {
|
|
||||||
func createEventTable(in queue: FMDatabaseQueue) -> Single<Bool> {
|
|
||||||
return mapTransactionToSingle(queue: queue) { db in
|
|
||||||
db.executeStatements(Entity.EventRecord.createTableSql(with: TableName.event.rawValue))
|
|
||||||
}
|
|
||||||
.do(onSuccess: { result in
|
|
||||||
cdPrint("createEventTable result: \(result)")
|
|
||||||
}, onError: { error in
|
|
||||||
cdPrint("createEventTable error: \(error)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapTransactionToSingle<T>(_ transaction: @escaping ((FMDatabase) throws -> T)) -> Single<T> {
|
|
||||||
return dbQueueRelay.compactMap({ $0 })
|
|
||||||
.take(1)
|
|
||||||
.asSingle()
|
|
||||||
.flatMap { [unowned self] queue -> Single<T> in
|
|
||||||
return self.mapTransactionToSingle(queue: queue, transaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapTransactionToSingle<T>(queue: FMDatabaseQueue, _ transaction: @escaping ((FMDatabase) throws -> T)) -> Single<T> {
|
|
||||||
return Single<T>.create { [weak self] (subscriber) -> Disposable in
|
|
||||||
self?.dbIOQueue.async {
|
|
||||||
queue.inDeferredTransaction { (db, rollback) in
|
|
||||||
do {
|
|
||||||
let data = try transaction(db)
|
|
||||||
subscriber(.success(data))
|
|
||||||
} catch {
|
|
||||||
rollback.pointee = true
|
|
||||||
cdPrint("inDeferredTransaction failed: \(error.localizedDescription)")
|
|
||||||
subscriber(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Disposables.create()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableUpdateNotification(_ tableName: String) -> Notification.Name {
|
|
||||||
return Notification.Name("Guru.Analytics.DB.Table.update-\(tableName)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateDB(in queue: FMDatabaseQueue) -> Single<Void> {
|
|
||||||
|
|
||||||
return mapTransactionToSingle(queue: queue) { [weak self] db in
|
|
||||||
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
|
|
||||||
while let nextVersion = self.dbVersion.nextVersion,
|
|
||||||
self.dbVersion < self.currentDBVersion {
|
|
||||||
switch nextVersion {
|
|
||||||
case .v_1:
|
|
||||||
()
|
|
||||||
case .v_2:
|
|
||||||
/// v_1 -> v_2
|
|
||||||
/// event表增加priority列
|
|
||||||
if !db.columnExists(Entity.EventRecord.CodingKeys.priority.rawValue, inTableWithName: TableName.event.rawValue) {
|
|
||||||
db.executeStatements("""
|
|
||||||
ALTER TABLE \(TableName.event.rawValue)
|
|
||||||
ADD \(Entity.EventRecord.CodingKeys.priority.rawValue) Integer DEFAULT \(Entity.EventRecord.Priority.DEFAULT.rawValue)
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
case .v_3:
|
|
||||||
/// v_2 -> v_3
|
|
||||||
/// event表增加transitionStatus列
|
|
||||||
if !db.columnExists(Entity.EventRecord.CodingKeys.transitionStatus.rawValue, inTableWithName: TableName.event.rawValue) {
|
|
||||||
db.executeStatements("""
|
|
||||||
ALTER TABLE \(TableName.event.rawValue)
|
|
||||||
ADD \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) Integer DEFAULT \(Entity.EventRecord.TransitionStatus.idle.rawValue)
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
self.dbVersion = nextVersion
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.do(onError: { error in
|
|
||||||
cdPrint("migrate db error: \(error)")
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetAllTransitionStatus(in queue: FMDatabaseQueue) -> Single<Void> {
|
|
||||||
return mapTransactionToSingle(queue: queue) { db in
|
|
||||||
let updateSQL =
|
|
||||||
"""
|
|
||||||
UPDATE \(TableName.event.rawValue)
|
|
||||||
SET \(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) = \(Entity.EventRecord.TransitionStatus.idle.rawValue)
|
|
||||||
"""
|
|
||||||
try db.executeUpdate(updateSQL, values: nil)
|
|
||||||
}
|
|
||||||
.do(onSuccess: { [weak self] (_) in
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
NotificationCenter.default.post(name: self.tableUpdateNotification(TableName.event.rawValue), object: nil)
|
|
||||||
}, onError: { error in
|
|
||||||
cdPrint("\(#function) error: \(error)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate extension Array where Element == String {
|
|
||||||
|
|
||||||
var joinedStringForSQL: String {
|
|
||||||
return self.map { "'\($0)'" }.joined(separator: ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension Database {
|
|
||||||
|
|
||||||
enum DBVersionHistory: String, Comparable {
|
|
||||||
case v_1
|
|
||||||
case v_2
|
|
||||||
case v_3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Database.DBVersionHistory {
|
|
||||||
|
|
||||||
static func < (lhs: Database.DBVersionHistory, rhs: Database.DBVersionHistory) -> Bool {
|
|
||||||
return lhs.versionNumber < rhs.versionNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var versionNumber: Int {
|
|
||||||
return Int(String(self.rawValue.split(separator: "_")[1])) ?? 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var nextVersion: Self? {
|
|
||||||
return .init(rawValue: "v_\(versionNumber + 1)")
|
|
||||||
}
|
|
||||||
|
|
||||||
static let initialVersion: Self = .v_1
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: cc72ba03590e04e4db8e3d9eb675d0d2
|
|
||||||
PluginImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
iconMap: {}
|
|
||||||
executionOrder: {}
|
|
||||||
defineConstraints: []
|
|
||||||
isPreloaded: 0
|
|
||||||
isOverridable: 0
|
|
||||||
isExplicitlyReferenced: 0
|
|
||||||
validateReferences: 1
|
|
||||||
platformData:
|
|
||||||
- first:
|
|
||||||
: Any
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
Exclude Android: 1
|
|
||||||
Exclude Editor: 1
|
|
||||||
Exclude Linux64: 1
|
|
||||||
Exclude OSXUniversal: 1
|
|
||||||
Exclude Win: 1
|
|
||||||
Exclude Win64: 1
|
|
||||||
Exclude iOS: 1
|
|
||||||
- first:
|
|
||||||
Any:
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings: {}
|
|
||||||
- first:
|
|
||||||
Editor: Editor
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
DefaultValueInitialized: true
|
|
||||||
- first:
|
|
||||||
iPhone: iOS
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings: {}
|
|
||||||
- first:
|
|
||||||
tvOS: tvOS
|
|
||||||
second:
|
|
||||||
enabled: 1
|
|
||||||
settings: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,681 +0,0 @@
|
||||||
//
|
|
||||||
// Manager.swift
|
|
||||||
// GuruAnalytics_iOS
|
|
||||||
//
|
|
||||||
// Created by 袁仕崇 on 16/11/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import RxCocoa
|
|
||||||
import RxSwift
|
|
||||||
|
|
||||||
internal class Manager {
|
|
||||||
|
|
||||||
// MARK: - temporary, will be removed soon
|
|
||||||
@available(*, deprecated, message: "used for debug, will be removed on any future released versions")
|
|
||||||
private var loggedEventsCount: Int = 0
|
|
||||||
|
|
||||||
@available(*, deprecated, message: "used for debug, will be removed on any future released versions")
|
|
||||||
private func accumulateLoggedEventsCount(_ count: Int) {
|
|
||||||
loggedEventsCount += count
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(*, deprecated, message: "used for debug, will be removed on any future released versions")
|
|
||||||
private var uploadedEventsCount: Int = 0
|
|
||||||
|
|
||||||
@available(*, deprecated, message: "used for debug, will be removed on any future released versions")
|
|
||||||
private func accumulateUploadedEventsCount(_ count: Int) {
|
|
||||||
uploadedEventsCount += count
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(*, deprecated, message: "used for debug, will be removed on any future released versions")
|
|
||||||
internal func debug_eventsStatistics(_ callback: @escaping (_ uploadedEventsCount: Int, _ loggedEventsCount: Int) -> Void) {
|
|
||||||
callback(uploadedEventsCount, loggedEventsCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - internal members
|
|
||||||
|
|
||||||
internal static let shared = Manager()
|
|
||||||
|
|
||||||
/// 时间维度,默认每1分钟后批量上传1次
|
|
||||||
private var scheduleInterval: TimeInterval = GuruAnalytics.uploadPeriodInSecond
|
|
||||||
|
|
||||||
/// 数量维度,默认满25条批量上传1次
|
|
||||||
private var numberOfCountPerConsume: Int = GuruAnalytics.batchLimit
|
|
||||||
|
|
||||||
/// event过期时间,默认7天
|
|
||||||
private var eventExpiredIntervel: TimeInterval = GuruAnalytics.eventExpiredSeconds
|
|
||||||
|
|
||||||
private var initializeTimeout: Double = GuruAnalytics.initializeTimeout
|
|
||||||
|
|
||||||
/// 根据时差计算的当前服务端时间
|
|
||||||
internal var serverNowMs: Int64 { serverInitialMs + (Date.absoluteTimeMs - serverSyncedAtAbsoluteMs)}
|
|
||||||
|
|
||||||
// MARK: - private members
|
|
||||||
|
|
||||||
private typealias PropertyName = GuruAnalytics.PropertyName
|
|
||||||
|
|
||||||
private let bag = DisposeBag()
|
|
||||||
|
|
||||||
private let db = Database()
|
|
||||||
|
|
||||||
private let ntwkMgr = NetworkManager()
|
|
||||||
|
|
||||||
/// 生成background 任务时,将 key 和当前任务的 disposeable 一一对应
|
|
||||||
private var taskKeyDisposableMap: [Int: Disposable] = [:]
|
|
||||||
|
|
||||||
/// 从数据库中一次性拉取最多条数
|
|
||||||
private var maxEventFetchingCount: Int = 100
|
|
||||||
|
|
||||||
/// 工作队列
|
|
||||||
private let workQueue = DispatchQueue.init(label: "com.guru.analytics.manager.work.queue", qos: .userInitiated)
|
|
||||||
///网络服务队列
|
|
||||||
private lazy var rxNetworkScheduler = SerialDispatchQueueScheduler(qos: .default, internalSerialQueueName: "com.guru.analytics.manager.rx.network.queue")
|
|
||||||
private lazy var rxConsumeScheduler = SerialDispatchQueueScheduler(qos: .default, internalSerialQueueName: "com.guru.analytics.manager.rx.consume.queue")
|
|
||||||
|
|
||||||
private lazy var rxWorkScheduler = SerialDispatchQueueScheduler.init(queue: workQueue, internalSerialQueueName: "com.guru.analytics.manager.rx.work.queue")
|
|
||||||
private let bgWorkQueue = DispatchQueue.init(label: "com.guru.analytics.manager.background.work.queue", qos: .background)
|
|
||||||
private lazy var rxBgWorkScheduler = SerialDispatchQueueScheduler.init(queue: bgWorkQueue, internalSerialQueueName: "com.guru.analytics.manager.background.work.queue")
|
|
||||||
|
|
||||||
/// 过期event记录已清除
|
|
||||||
private let outdatedEventsCleared = BehaviorSubject(value: false)
|
|
||||||
|
|
||||||
/// 服务端时间
|
|
||||||
private var serverInitialMs = Date().msSince1970 {
|
|
||||||
didSet {
|
|
||||||
serverSyncedAtAbsoluteMs = Date.absoluteTimeMs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var serverSyncedAtAbsoluteMs = Date.absoluteTimeMs
|
|
||||||
private let startAt = Date()
|
|
||||||
/// 服务器时间已同步信号
|
|
||||||
private let _serverTimeSynced = BehaviorRelay(value: false)
|
|
||||||
private var serverNowMsSingle: Single<Int64> {
|
|
||||||
|
|
||||||
guard _serverTimeSynced.value == false else {
|
|
||||||
return .just(serverNowMs)
|
|
||||||
}
|
|
||||||
return _serverTimeSynced.observe(on: rxNetworkScheduler)
|
|
||||||
.filter { $0 }
|
|
||||||
.take(1).asSingle()
|
|
||||||
.timeout(.seconds(10), scheduler: rxNetworkScheduler)
|
|
||||||
.catchAndReturn(false)
|
|
||||||
.map({ [weak self] _ in
|
|
||||||
return self?.serverNowMs ?? 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 统计fg起始时间
|
|
||||||
private var fgStartAtAbsoluteMs = Date.absoluteTimeMs
|
|
||||||
private var fgAccumulateTimer: Disposable? = nil
|
|
||||||
|
|
||||||
/// 内存中user property 信息
|
|
||||||
private var userProperty: Observable<[String : String]> {
|
|
||||||
let p = userPropertyUpdated.startWith(()).observe(on: rxWorkScheduler).flatMap { [weak self] _ -> Observable<[String : String]> in
|
|
||||||
guard let `self` = self else { return .just([:]) }
|
|
||||||
return .create({ subscriber in
|
|
||||||
subscriber.onNext(self._userProperty)
|
|
||||||
subscriber.onCompleted()
|
|
||||||
// debugPrint("userProperty thread queueName: \(Thread.current.queueName)")
|
|
||||||
return Disposables.create()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
let latency = self.initializeTimeout - Date().timeIntervalSince(self.startAt)
|
|
||||||
let intLatency = Int(latency)
|
|
||||||
|
|
||||||
guard latency > 0 else {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.filter({ property in
|
|
||||||
/// 需要等待以下userproperty已设置
|
|
||||||
/// PropertyName.deviceId
|
|
||||||
/// PropertyName.uid
|
|
||||||
/// PropertyName.firebaseId
|
|
||||||
guard let deviceId = property[PropertyName.deviceId.rawValue], !deviceId.isEmpty,
|
|
||||||
let uid = property[PropertyName.uid.rawValue], !uid.isEmpty,
|
|
||||||
let firebaseId = property[PropertyName.firebaseId.rawValue], !firebaseId.isEmpty else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.timeout(.milliseconds(intLatency), scheduler: rxNetworkScheduler)
|
|
||||||
.catch { _ in
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var _userProperty: [String : String] = [:] {
|
|
||||||
didSet {
|
|
||||||
userPropertyUpdated.onNext(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var userPropertyUpdated = PublishSubject<Void>()
|
|
||||||
|
|
||||||
/// 同步服务器时间触发器
|
|
||||||
private let syncServerTrigger = PublishSubject<Void>()
|
|
||||||
|
|
||||||
/// 轮询上传event任务
|
|
||||||
private var pollingUploadTask: Disposable?
|
|
||||||
|
|
||||||
/// 重置轮询上传触发器
|
|
||||||
private let reschedulePollingTrigger = BehaviorSubject(value: ())
|
|
||||||
|
|
||||||
/// 记录events相关的logger
|
|
||||||
private lazy var eventsLogger: LoggerManager = {
|
|
||||||
let l = LoggerManager(logCategoryName: "eventLogs")
|
|
||||||
return l
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// 将错误上报给上层的
|
|
||||||
private typealias InternalEventReporter = ((_ eventCode: Int, _ info: String) -> Void)
|
|
||||||
private var internalEventReporter: InternalEventReporter?
|
|
||||||
|
|
||||||
private init() {
|
|
||||||
|
|
||||||
// first open
|
|
||||||
logFirstOpenIfNeeded()
|
|
||||||
|
|
||||||
// 监听事件
|
|
||||||
setupOberving()
|
|
||||||
|
|
||||||
// 检查旧数据
|
|
||||||
clearOutdatedEventsIfNeeded()
|
|
||||||
|
|
||||||
// 设置轮询上传任务
|
|
||||||
setupPollingUpload()
|
|
||||||
|
|
||||||
// 先打一个fg
|
|
||||||
logFirstFgEvent()
|
|
||||||
|
|
||||||
ntwkMgr.networkErrorReporter = self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - internal functions
|
|
||||||
internal extension Manager {
|
|
||||||
|
|
||||||
func logEvent(_ eventName: String, parameters: [String : Any]?, priority: Entity.EventRecord.Priority = .DEFAULT) {
|
|
||||||
_ = _logEvent(eventName, parameters: parameters, priority: priority)
|
|
||||||
.subscribe()
|
|
||||||
.disposed(by: bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setUserProperty(_ value: String, forName name: String) {
|
|
||||||
eventsLogger.verbose(#function + "name: \(name) value: \(value)")
|
|
||||||
workQueue.async { [weak self] in
|
|
||||||
self?._userProperty[name] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeUserProperties(forNames names: [String]) {
|
|
||||||
eventsLogger.verbose(#function + "names: \(names)")
|
|
||||||
workQueue.async { [weak self] in
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
var temp = self._userProperty
|
|
||||||
for name in names {
|
|
||||||
temp.removeValue(forKey: name)
|
|
||||||
}
|
|
||||||
self._userProperty = temp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setScreen(_ name: String) {
|
|
||||||
setUserProperty(name, forName: PropertyName.screen.rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func constructEvent(_ eventName: String,
|
|
||||||
parameters: [String : Any]?,
|
|
||||||
timestamp: Int64,
|
|
||||||
priority: Entity.EventRecord.Priority) -> Single<Entity.EventRecord> {
|
|
||||||
|
|
||||||
return userProperty.take(1).observe(on: rxWorkScheduler).asSingle().flatMap { p in
|
|
||||||
.create { subscriber in
|
|
||||||
do {
|
|
||||||
debugPrint("userProperty thread queueName: \(Thread.current.queueName) count: \(p.count)")
|
|
||||||
var userProperty = p
|
|
||||||
var eventParam = parameters ?? [:]
|
|
||||||
|
|
||||||
// append screen
|
|
||||||
if let screen = userProperty.removeValue(forKey: PropertyName.screen.rawValue) {
|
|
||||||
eventParam[PropertyName.screen.rawValue] = screen
|
|
||||||
}
|
|
||||||
|
|
||||||
let userInfo = Entity.UserInfo(
|
|
||||||
uid: userProperty.removeValue(forKey: PropertyName.uid.rawValue),
|
|
||||||
deviceId: userProperty.removeValue(forKey: PropertyName.deviceId.rawValue),
|
|
||||||
adjustId: userProperty.removeValue(forKey: PropertyName.adjustId.rawValue),
|
|
||||||
adId: userProperty.removeValue(forKey: PropertyName.adId.rawValue),
|
|
||||||
firebaseId: userProperty.removeValue(forKey: PropertyName.firebaseId.rawValue)
|
|
||||||
)
|
|
||||||
|
|
||||||
let event = try Entity.Event(timestamp: timestamp,
|
|
||||||
event: eventName,
|
|
||||||
userInfo: userInfo,
|
|
||||||
parameters: eventParam,
|
|
||||||
properties: userProperty)
|
|
||||||
let eventRecord = Entity.EventRecord(eventName: event.event, event: event, priority: priority)
|
|
||||||
subscriber(.success(eventRecord))
|
|
||||||
} catch {
|
|
||||||
subscriber(.failure(error))
|
|
||||||
}
|
|
||||||
return Disposables.create()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func eventsLogsArchive(_ callback: @escaping (URL?) -> Void) {
|
|
||||||
eventsLogger.logFilesZipArchive()
|
|
||||||
.subscribe(onSuccess: { url in
|
|
||||||
callback(url)
|
|
||||||
}, onFailure: { error in
|
|
||||||
callback(nil)
|
|
||||||
cdPrint("events logs archive error: \(error)")
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func eventsLogsDirURL(_ callback: @escaping (URL?) -> Void) {
|
|
||||||
eventsLogger.logFilesDirURL()
|
|
||||||
.subscribe(onSuccess: { url in
|
|
||||||
callback(url)
|
|
||||||
}, onFailure: { error in
|
|
||||||
callback(nil)
|
|
||||||
cdPrint("events logs archive error: \(error)")
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerInternalEventObserver(reportCallback: @escaping (_ eventCode: Int, _ info: String) -> Void) {
|
|
||||||
self.internalEventReporter = reportCallback
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUserProperties() -> [String : String] {
|
|
||||||
return _userProperty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - private functions
|
|
||||||
private extension Manager {
|
|
||||||
|
|
||||||
func setupOberving() {
|
|
||||||
|
|
||||||
syncServerTrigger
|
|
||||||
.debounce(.seconds(1), scheduler: rxConsumeScheduler)
|
|
||||||
.subscribe(onNext: { [weak self] _ in
|
|
||||||
self?.syncServerTime()
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
|
|
||||||
var activeNoti = NotificationCenter.default.rx.notification(UIApplication.didBecomeActiveNotification)
|
|
||||||
|
|
||||||
if UIApplication.shared.applicationState == .active {
|
|
||||||
activeNoti = activeNoti.startWith(.init(name: UIApplication.didBecomeActiveNotification))
|
|
||||||
}
|
|
||||||
|
|
||||||
activeNoti
|
|
||||||
.subscribe(onNext: { [weak self] _ in
|
|
||||||
self?.syncServerTrigger.onNext(())
|
|
||||||
// fg计时器
|
|
||||||
self?.setupFgAccumulateTimer()
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
|
|
||||||
NotificationCenter.default.rx.notification(UIApplication.didEnterBackgroundNotification)
|
|
||||||
.subscribe(onNext: { [weak self] _ in
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
//这里log fg和上传events任务并行关系改为前后依赖关系
|
|
||||||
_ = self.logForegroundDuration()
|
|
||||||
.catchAndReturn(())
|
|
||||||
.map { self.consumeEvents() }
|
|
||||||
.subscribe()
|
|
||||||
self._serverTimeSynced.accept(false)
|
|
||||||
self.invalidFgAccumulateTimer()
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func syncServerTime() {
|
|
||||||
//有网时立即同步,无网时等待有网后同步
|
|
||||||
ntwkMgr.reachableObservable.filter { $0 }.map { _ in }.take(1).asSingle()
|
|
||||||
.flatMap { [weak self] _ -> Single<Int64> in
|
|
||||||
guard let `self` = self else { return Observable.empty().asSingle()}
|
|
||||||
return self.ntwkMgr.syncServerTime()
|
|
||||||
}
|
|
||||||
.observe(on: rxNetworkScheduler)
|
|
||||||
.subscribe(onSuccess: { [weak self] ms in
|
|
||||||
self?.serverInitialMs = ms
|
|
||||||
self?._serverTimeSynced.accept(true)
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logForegroundDuration() -> Single<Void> {
|
|
||||||
return _logEvent(GuruAnalytics.fgEvent.name, parameters: [GuruAnalytics.fgEvent.paramKeyType.duration.rawValue : fgDurationMs()])
|
|
||||||
.observe(on: MainScheduler.asyncInstance)
|
|
||||||
.do(onSuccess: { _ in
|
|
||||||
UserDefaults.fgAccumulatedDuration = 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearOutdatedEventsIfNeeded() {
|
|
||||||
|
|
||||||
/// 1. 删除过期的数据
|
|
||||||
serverNowMsSingle
|
|
||||||
.flatMap({ [weak self] serverNowMs -> Single<Void> in
|
|
||||||
guard let `self` = self else { return .just(()) }
|
|
||||||
let earlierThan: Int64 = serverNowMs - self.eventExpiredIntervel.int64Ms
|
|
||||||
return self.db.removeOutdatedEventRecords(earlierThan: earlierThan)
|
|
||||||
})
|
|
||||||
.catch({ error in
|
|
||||||
cdPrint("remove outdated records error: \(error)")
|
|
||||||
return .just(())
|
|
||||||
})
|
|
||||||
.subscribe(onSuccess: { [weak self] _ in
|
|
||||||
self?.outdatedEventsCleared.onNext(true)
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logFirstOpenIfNeeded() {
|
|
||||||
|
|
||||||
if let t = UserDefaults.defaults?.value(forKey: UserDefaults.firstOpenTimeKey),
|
|
||||||
let firstOpenTimeMs = t as? Int64 {
|
|
||||||
setUserProperty("\(firstOpenTimeMs)", forName: PropertyName.firstOpenTime.rawValue)
|
|
||||||
} else {
|
|
||||||
/// log first open event
|
|
||||||
logEvent(GuruAnalytics.firstOpenEvent.name, parameters: nil, priority: .EMERGENCE)
|
|
||||||
|
|
||||||
/// save first open time
|
|
||||||
/// set to userProperty
|
|
||||||
let firstOpenAt = Date()
|
|
||||||
|
|
||||||
let saveFirstOpenTime = { [weak self] (ms: Int64) -> Void in
|
|
||||||
UserDefaults.defaults?.set(ms, forKey: UserDefaults.firstOpenTimeKey)
|
|
||||||
self?.setUserProperty("\(ms)", forName: PropertyName.firstOpenTime.rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
serverNowMsSingle
|
|
||||||
.subscribe(onSuccess: { _ in
|
|
||||||
let latency = Date().timeIntervalSince(firstOpenAt)
|
|
||||||
let adjustedFirstOpenTimeMs = self.serverInitialMs - latency.int64Ms
|
|
||||||
saveFirstOpenTime(adjustedFirstOpenTimeMs)
|
|
||||||
}, onFailure: { error in
|
|
||||||
cdPrint("waiting for server time syncing error: \(error)")
|
|
||||||
saveFirstOpenTime(firstOpenAt.timeIntervalSince1970.int64Ms)
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func _logEvent(_ eventName: String, parameters: [String : Any]?, priority: Entity.EventRecord.Priority = .DEFAULT) -> Single<Void> {
|
|
||||||
eventsLogger.verbose(#function + " eventName: \(eventName)" + " params: \(parameters?.jsonString() ?? "")")
|
|
||||||
return { [weak self] () -> Single<Void> in
|
|
||||||
guard let `self` = self else { return Observable<Void>.empty().asSingle() }
|
|
||||||
return self.serverNowMsSingle
|
|
||||||
.flatMap { self.constructEvent(eventName, parameters: parameters, timestamp: $0, priority: priority) }
|
|
||||||
.flatMap { self.db.addEventRecords($0) }
|
|
||||||
.do(onSuccess: { _ in
|
|
||||||
self.accumulateLoggedEventsCount(1)
|
|
||||||
self.eventsLogger.verbose("log event success")
|
|
||||||
}, onError: { error in
|
|
||||||
self.eventsLogger.error("log event error: \(error)")
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 轮询上传相关
|
|
||||||
private extension Manager {
|
|
||||||
|
|
||||||
typealias TaskCallback = (() -> Void)
|
|
||||||
typealias Task = ((@escaping TaskCallback, Int) -> Void)
|
|
||||||
|
|
||||||
func performBackgroundTask(task: @escaping Task) -> Single<Void> {
|
|
||||||
return Single.create { [weak self] subscriber in
|
|
||||||
var backgroundTaskID: UIBackgroundTaskIdentifier?
|
|
||||||
|
|
||||||
let stopTaskHandler = {
|
|
||||||
///结束任务时需要找到对应的 dispose 取消当前任务
|
|
||||||
guard let taskId = backgroundTaskID,
|
|
||||||
let disposable = self?.taskKeyDisposableMap[taskId.rawValue] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cdPrint("[performBackgroundTask] performBackgroundTask expired: \(backgroundTaskID?.rawValue ?? -1)")
|
|
||||||
disposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request the task assertion and save the ID.
|
|
||||||
backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "com.guru.analytics.manager.background.task", expirationHandler: {
|
|
||||||
// End the task if time expires.
|
|
||||||
self?.eventsLogger.verbose("performBackgroundTask expirationHandler: \(backgroundTaskID?.rawValue ?? -1)")
|
|
||||||
stopTaskHandler()
|
|
||||||
})
|
|
||||||
|
|
||||||
self?.eventsLogger.verbose("performBackgroundTask start: \(backgroundTaskID?.rawValue ?? -1)")
|
|
||||||
if let taskID = backgroundTaskID {
|
|
||||||
task({
|
|
||||||
self?.eventsLogger.verbose("performBackgroundTask finish: \(taskID.rawValue)")
|
|
||||||
subscriber(.success(()))
|
|
||||||
}, taskID.rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Disposables.create {
|
|
||||||
if var taskID = backgroundTaskID {
|
|
||||||
self?.eventsLogger.verbose("performBackgroundTask dispose: \(taskID.rawValue)")
|
|
||||||
UIApplication.shared.endBackgroundTask(taskID)
|
|
||||||
taskID = .invalid
|
|
||||||
backgroundTaskID = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.subscribe(on: rxBgWorkScheduler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 上传数据库中的event
|
|
||||||
func consumeEvents() {
|
|
||||||
guard GuruAnalytics.enableUpload else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.eventsLogger.verbose("consumeEvents start")
|
|
||||||
performBackgroundTask { [weak self] callback, taskId in
|
|
||||||
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
cdPrint("consumeEvents start background task")
|
|
||||||
// 等待清理过期记录完成
|
|
||||||
let disposable = outdatedEventsCleared
|
|
||||||
.filter { $0 }
|
|
||||||
.take(1)
|
|
||||||
.observe(on: rxBgWorkScheduler)
|
|
||||||
.asSingle()
|
|
||||||
.flatMap { _ -> Single<[Entity.EventRecord]> in
|
|
||||||
self.eventsLogger.verbose("consumeEvents fetchEventRecordsToUpload")
|
|
||||||
///step1: 拉取数据库记录
|
|
||||||
return self.db.fetchEventRecordsToUpload(limit: self.maxEventFetchingCount)
|
|
||||||
}
|
|
||||||
.map { records -> [[Entity.EventRecord]] in
|
|
||||||
/// step2: 将event数组分割成若干批次,numberOfCountPerConsume个一批
|
|
||||||
/// self.eventsLogger.verbose("consumeEvents fetchEventRecordsToUpload")
|
|
||||||
self.eventsLogger.verbose("consumeEvents fetchEventRecordsToUpload result: \(records.count)")
|
|
||||||
return records.chunked(into: self.numberOfCountPerConsume)
|
|
||||||
}
|
|
||||||
.flatMap({ batches -> Single<[[Entity.EventRecord]]> in
|
|
||||||
|
|
||||||
guard batches.count > 0 else { return .just([]) }
|
|
||||||
|
|
||||||
/// 监听网络信号
|
|
||||||
return self.ntwkMgr.reachableObservable.filter { $0 }
|
|
||||||
.take(1).asSingle()
|
|
||||||
.map { _ in batches }
|
|
||||||
})
|
|
||||||
.map { batches -> [Single<[String]>] in
|
|
||||||
/// step3: 转为批次上传任务
|
|
||||||
self.eventsLogger.verbose("consumeEvents uploadEvents")
|
|
||||||
return batches.map { records in
|
|
||||||
return self.ntwkMgr.uploadEvents(records)
|
|
||||||
.do(onSuccess: { t in
|
|
||||||
self.eventsLogger.verbose("consumeEvents upload events succeed: \(t.eventsJson)")
|
|
||||||
})
|
|
||||||
.catch({ error in
|
|
||||||
self.eventsLogger.error("consumeEvents upload events error: \(error)")
|
|
||||||
// 上传失败,移除对应的缓存ID
|
|
||||||
let recordIds = records.map { $0.recordId }
|
|
||||||
return self.db.resetTransitionStatus(for: recordIds)
|
|
||||||
.map { _ in ([], "") }
|
|
||||||
})
|
|
||||||
.map { $0.recordIDs }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flatMap { uploadBatches -> Single<[String]> in
|
|
||||||
guard uploadBatches.count > 0 else { return .just([]) }
|
|
||||||
/// 合并上传结果
|
|
||||||
return Observable.from(uploadBatches)
|
|
||||||
.merge()
|
|
||||||
.toArray().map { batches -> [String] in batches.flatMap { $0 } }
|
|
||||||
}
|
|
||||||
.flatMap { recordIDs -> Single<Void> in
|
|
||||||
self.accumulateUploadedEventsCount(recordIDs.count)
|
|
||||||
/// step4: 删除数据库中对应记录
|
|
||||||
return self.db.deleteEventRecords(recordIDs)
|
|
||||||
.catch { error in
|
|
||||||
cdPrint("consumeEvents delete events from DB error: \(error)")
|
|
||||||
return .just(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.observe(on: self.rxBgWorkScheduler)
|
|
||||||
.subscribe(onFailure: { error in
|
|
||||||
cdPrint("consumeEvents error: \(error)")
|
|
||||||
}, onDisposed: { [weak self] in
|
|
||||||
self?.taskKeyDisposableMap.removeValue(forKey: taskId)
|
|
||||||
cdPrint("consumeEvents onDisposed")
|
|
||||||
callback()
|
|
||||||
})
|
|
||||||
|
|
||||||
taskKeyDisposableMap[taskId] = disposable
|
|
||||||
}
|
|
||||||
.subscribe()
|
|
||||||
.disposed(by: bag)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func startPollingUpload() {
|
|
||||||
pollingUploadTask?.dispose()
|
|
||||||
pollingUploadTask = nil
|
|
||||||
|
|
||||||
// 每scheduleInterval时间间隔启动一次,立即启动一次
|
|
||||||
let timer = Observable<Int>.timer(.seconds(0), period: .milliseconds(Int(scheduleInterval.int64Ms)),
|
|
||||||
scheduler: rxConsumeScheduler)
|
|
||||||
.do(onNext: { _ in
|
|
||||||
cdPrint("consumeEvents timer")
|
|
||||||
})
|
|
||||||
|
|
||||||
// 每满numberOfCountPerConsume个数启动一次,立即启动一次
|
|
||||||
let counter = db.uploadableEventRecordCountOb()
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.compactMap({ [weak self] count -> Int? in
|
|
||||||
cdPrint("consumeEvents uploadableEventRecordCountOb count: \(count) numberOfCountPerConsume: \(self?.numberOfCountPerConsume)")
|
|
||||||
guard let `self` = self,
|
|
||||||
count >= self.numberOfCountPerConsume else { return nil }
|
|
||||||
return count
|
|
||||||
})
|
|
||||||
.map { _ in }
|
|
||||||
.startWith(())
|
|
||||||
|
|
||||||
pollingUploadTask = Observable.combineLatest(timer, counter)
|
|
||||||
.throttle(.seconds(1), scheduler: rxConsumeScheduler)
|
|
||||||
.flatMap({ [weak self] t -> Single<(Int, Void)> in
|
|
||||||
guard let `self` = self else { return .just(t) }
|
|
||||||
return Observable.combineLatest(self.db.hasFgEventRecord().asObservable(), self.db.uploadableEventRecordCount().asObservable())
|
|
||||||
.take(1).asSingle()
|
|
||||||
.flatMap({ (hasFgEventInDb, eventsCount) -> Single<(Int, Void)> in
|
|
||||||
guard !hasFgEventInDb, eventsCount > 0 else {
|
|
||||||
return .just(t)
|
|
||||||
}
|
|
||||||
return self.logForegroundDuration().catchAndReturn(()).map({ _ in t })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.subscribe(onNext: { [weak self] (timer, counter) in
|
|
||||||
self?.consumeEvents()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupPollingUpload() {
|
|
||||||
reschedulePollingTrigger
|
|
||||||
.debounce(.seconds(1), scheduler: rxConsumeScheduler)
|
|
||||||
.subscribe(onNext: { [weak self] _ in
|
|
||||||
self?.startPollingUpload()
|
|
||||||
})
|
|
||||||
.disposed(by: bag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logFirstFgEvent() {
|
|
||||||
_ = Single.just(()).delay(.milliseconds(500), scheduler: MainScheduler.asyncInstance)
|
|
||||||
.flatMap({ [weak self] _ in
|
|
||||||
self?.logForegroundDuration() ?? .just(())
|
|
||||||
})
|
|
||||||
.subscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - fg相关
|
|
||||||
private extension Manager {
|
|
||||||
|
|
||||||
func setupFgAccumulateTimer() {
|
|
||||||
invalidFgAccumulateTimer()
|
|
||||||
fgStartAtAbsoluteMs = Date.absoluteTimeMs
|
|
||||||
fgAccumulateTimer = Observable<Int>.timer(.seconds(0), period: .seconds(1), scheduler: MainScheduler.asyncInstance)
|
|
||||||
.subscribe(onNext: { [weak self] _ in
|
|
||||||
guard let `self` = self else { return }
|
|
||||||
UserDefaults.fgAccumulatedDuration = self.fgDurationMs()
|
|
||||||
}, onDisposed: {
|
|
||||||
cdPrint("fg accumulate timer disposed")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func invalidFgAccumulateTimer() {
|
|
||||||
fgAccumulateTimer?.dispose()
|
|
||||||
fgAccumulateTimer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 前台停留时长
|
|
||||||
func fgDurationMs() -> Int64 {
|
|
||||||
let slice = Date.absoluteTimeMs - fgStartAtAbsoluteMs
|
|
||||||
fgStartAtAbsoluteMs = Date.absoluteTimeMs
|
|
||||||
// cdPrint("accumulate fg duration: \(slice)")
|
|
||||||
let totalDuration = UserDefaults.fgAccumulatedDuration + slice
|
|
||||||
// cdPrint("total fg duration: \(totalDuration)")
|
|
||||||
return totalDuration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Manager: GuruAnalyticsNetworkErrorReportDelegate {
|
|
||||||
func reportError(networkError: GuruAnalyticsNetworkError) {
|
|
||||||
|
|
||||||
enum UserInfoKey: String, Encodable {
|
|
||||||
case httpCode = "h_c"
|
|
||||||
case errorCode = "e_c"
|
|
||||||
case url, msg
|
|
||||||
}
|
|
||||||
|
|
||||||
let errorCode = networkError.internalErrorCategory.rawValue
|
|
||||||
let userInfo = (networkError.originError as NSError).userInfo
|
|
||||||
var info: [UserInfoKey : String] = [
|
|
||||||
.url : (userInfo[NSURLErrorFailingURLStringErrorKey] as? String) ?? "",
|
|
||||||
.msg : networkError.originError.localizedDescription,
|
|
||||||
]
|
|
||||||
|
|
||||||
if let httpCode = networkError.httpStatusCode {
|
|
||||||
info[.httpCode] = "\(httpCode)"
|
|
||||||
} else {
|
|
||||||
info[.errorCode] = "\((networkError.originError as NSError).code)"
|
|
||||||
}
|
|
||||||
|
|
||||||
info = info.compactMapValues { $0.isEmpty ? nil : $0 }
|
|
||||||
|
|
||||||
let jsonString = info.asString ?? ""
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
|
||||||
self?.internalEventReporter?(errorCode, jsonString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
//
|
|
||||||
// UserDefaults.swift
|
|
||||||
// GuruAnalytics
|
|
||||||
//
|
|
||||||
// Created by mayue on 2022/11/21.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
internal enum UserDefaults {
|
|
||||||
|
|
||||||
static let defaults = Foundation.UserDefaults(suiteName: "com.guru.guru_analytics_lib")
|
|
||||||
|
|
||||||
static var eventsServerHost: String? {
|
|
||||||
|
|
||||||
get {
|
|
||||||
return defaults?.value(forKey: eventsServerHostKey) as? String
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
var host = newValue
|
|
||||||
let h_sch = "http://"
|
|
||||||
let hs_sch = "https://"
|
|
||||||
host?.deletePrefix(h_sch)
|
|
||||||
host?.deletePrefix(hs_sch)
|
|
||||||
host?.trimmed(in: .whitespacesAndNewlines.union(.init(charactersIn: "/")))
|
|
||||||
defaults?.set(host, forKey: eventsServerHostKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static var fgAccumulatedDuration: Int64 {
|
|
||||||
get {
|
|
||||||
return defaults?.value(forKey: fgDurationKey) as? Int64 ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
defaults?.set(newValue, forKey: fgDurationKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UserDefaults {
|
|
||||||
|
|
||||||
static var firstOpenTimeKey: String {
|
|
||||||
return "app.first.open.timestamp"
|
|
||||||
}
|
|
||||||
|
|
||||||
static var dbVersionKey: String {
|
|
||||||
return "db.version"
|
|
||||||
}
|
|
||||||
|
|
||||||
static var hostsMapKey: String {
|
|
||||||
return "hosts.map"
|
|
||||||
}
|
|
||||||
|
|
||||||
static var eventsServerHostKey: String {
|
|
||||||
return "events.server.host"
|
|
||||||
}
|
|
||||||
|
|
||||||
static var fgDurationKey: String {
|
|
||||||
return "fg.duration.ms"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: b2af081c64bfe4af7b232b8132d01544
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
//
|
|
||||||
// GuruAnalyticsErrorHandleDelegate.swift
|
|
||||||
// Alamofire
|
|
||||||
//
|
|
||||||
// Created by mayue on 2023/10/27.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
internal enum GuruAnalyticsNetworkLayerErrorCategory: Int {
|
|
||||||
case unknown = -100
|
|
||||||
case serverAPIError = 101
|
|
||||||
case responseParsingError = 102
|
|
||||||
case googleDNSServiceError = 106
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc internal protocol GuruAnalyticsNetworkErrorReportDelegate {
|
|
||||||
func reportError(networkError: GuruAnalyticsNetworkError) -> Void
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class GuruAnalyticsNetworkError: NSError {
|
|
||||||
private(set) var httpStatusCode: Int?
|
|
||||||
private(set) var originError: Error
|
|
||||||
private(set) var internalErrorCategory: GuruAnalyticsNetworkLayerErrorCategory
|
|
||||||
|
|
||||||
init(httpStatusCode: Int? = nil, internalErrorCategory: GuruAnalyticsNetworkLayerErrorCategory, originError: Error) {
|
|
||||||
self.httpStatusCode = httpStatusCode
|
|
||||||
self.originError = originError
|
|
||||||
self.internalErrorCategory = internalErrorCategory
|
|
||||||
super.init(domain: "com.guru.analytics.network.layer", code: internalErrorCategory.rawValue, userInfo: (originError as NSError).userInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 77961084d08bf4074afb847d866d89e0
|
|
||||||
PluginImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
iconMap: {}
|
|
||||||
executionOrder: {}
|
|
||||||
defineConstraints: []
|
|
||||||
isPreloaded: 0
|
|
||||||
isOverridable: 0
|
|
||||||
isExplicitlyReferenced: 0
|
|
||||||
validateReferences: 1
|
|
||||||
platformData:
|
|
||||||
- first:
|
|
||||||
: Any
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
Exclude Android: 1
|
|
||||||
Exclude Editor: 1
|
|
||||||
Exclude Linux64: 1
|
|
||||||
Exclude OSXUniversal: 1
|
|
||||||
Exclude Win: 1
|
|
||||||
Exclude Win64: 1
|
|
||||||
Exclude iOS: 1
|
|
||||||
- first:
|
|
||||||
Android: Android
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: ARMv7
|
|
||||||
- first:
|
|
||||||
Any:
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings: {}
|
|
||||||
- first:
|
|
||||||
Editor: Editor
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
DefaultValueInitialized: true
|
|
||||||
OS: AnyOS
|
|
||||||
- first:
|
|
||||||
Standalone: Linux64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: OSXUniversal
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: Win
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
Standalone: Win64
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
CPU: AnyCPU
|
|
||||||
- first:
|
|
||||||
iPhone: iOS
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
AddToEmbeddedBinaries: false
|
|
||||||
CPU: AnyCPU
|
|
||||||
CompileFlags:
|
|
||||||
FrameworkDependencies:
|
|
||||||
- first:
|
|
||||||
tvOS: tvOS
|
|
||||||
second:
|
|
||||||
enabled: 1
|
|
||||||
settings: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue