Skip to content

Commit db420a7

Browse files
chore: [SDK-4406] prepare Unity demo app for Appium E2E tests (#869)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent c1b60e4 commit db420a7

68 files changed

Lines changed: 2745 additions & 1083 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
-keep class com.onesignal.** { *; }
22

33
# Work around for IllegalStateException with kotlinx-coroutines-android
4-
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
4+
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
Binary file not shown.

com.onesignal.unity.android/Editor/OneSignalConfig.androidlib/src/main/res/raw/notification.wav.meta

Lines changed: 0 additions & 7 deletions
This file was deleted.

examples/demo/.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1+
# Default App ID (used when ONESIGNAL_APP_ID is empty or missing): 77e32082-ea27-42e3-a898-c72e141824ef
2+
ONESIGNAL_APP_ID=your-onesignal-app-id
13
ONESIGNAL_API_KEY=your_rest_api_key
4+
E2E_MODE=false
5+
6+
# Optional: Android Notification Channel ID for the WITH SOUND test notification.
7+
# Create one in your OneSignal dashboard under Settings > Android Notification Categories.
8+
ONESIGNAL_ANDROID_CHANNEL_ID=

examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
using UnityEditor.iOS.Xcode.Extensions;
3535
using System.IO;
3636
using System.Linq;
37+
using System.Text.RegularExpressions;
3738

3839
namespace App.Editor.iOS
3940
{
@@ -53,6 +54,9 @@ public class BuildPostProcessor : IPostprocessBuildWithReport
5354
"OneSignalWidgetLiveActivity.swift",
5455
};
5556

57+
private static readonly string SoundsSourceDir = Path.Combine("iOS", "Sounds");
58+
private static readonly string[] CustomSoundFiles = new string[] { "vine_boom.wav" };
59+
5660
/// <summary>
5761
/// must be between 40 and 50 to ensure that it's not overriden by Podfile generation (40) and that it's
5862
/// added before "pod install" (50)
@@ -76,6 +80,7 @@ public void OnPostprocessBuild(BuildReport report)
7680

7781
EnableAppForLiveActivities(report.summary.outputPath);
7882
CreateWidgetExtension(report.summary.outputPath);
83+
AddCustomSoundsToMainTarget(report.summary.outputPath);
7984

8085
Debug.Log("BuildPostProcessor.OnPostprocessBuild complete");
8186
}
@@ -102,6 +107,43 @@ static void CreateWidgetExtension(string outputPath)
102107
AddWidgetExtensionToPodFile(outputPath);
103108
}
104109

110+
/// <summary>
111+
/// Copies bundled .wav files into the Xcode project and registers them on the main
112+
/// target's Resources build phase so `ios_sound` payload values resolve to a real
113+
/// `UNNotificationSound` resource in the .app bundle.
114+
/// </summary>
115+
static void AddCustomSoundsToMainTarget(string outputPath)
116+
{
117+
var project = new PBXProject();
118+
var projectPath = PBXProject.GetPBXProjectPath(outputPath);
119+
project.ReadFromString(File.ReadAllText(projectPath));
120+
121+
var mainTargetGuid = project.GetUnityMainTargetGuid();
122+
123+
foreach (var fileName in CustomSoundFiles)
124+
{
125+
var sourcePath = Path.Combine(SoundsSourceDir, fileName);
126+
if (!File.Exists(sourcePath))
127+
{
128+
Debug.LogWarning(
129+
$"Custom sound asset missing at {sourcePath}; skipping iOS bundling."
130+
);
131+
continue;
132+
}
133+
134+
var destAbsolutePath = Path.Combine(outputPath, fileName);
135+
File.Copy(sourcePath, destAbsolutePath, true);
136+
137+
if (!string.IsNullOrEmpty(project.FindFileGuidByProjectPath(fileName)))
138+
continue;
139+
140+
var fileGuid = project.AddFile(fileName, fileName);
141+
project.AddFileToBuild(mainTargetGuid, fileGuid);
142+
}
143+
144+
project.WriteToFile(projectPath);
145+
}
146+
105147
static void AddWidgetExtensionToProject(string outputPath)
106148
{
107149
var project = new PBXProject();
@@ -159,12 +201,63 @@ static void AddWidgetExtensionToPodFile(string outputPath)
159201
return;
160202
}
161203

204+
// Keep the widget extension pinned to the same OneSignalXCFramework version as the
205+
// core plugin so CocoaPods can resolve a single shared version across targets.
206+
var requiredVersion = ResolveOneSignalXCFrameworkVersion();
207+
var versionConstraint =
208+
requiredVersion != null ? $"'{requiredVersion}'" : "'>= 5.0.2', '< 6.0.0'";
209+
var requiredTarget =
210+
$"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', {versionConstraint}\nend\n";
211+
162212
var podfile = File.ReadAllText(podfilePath);
163-
podfile +=
164-
$"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', '>= 5.0.2', '< 6.0.0'\nend\n";
213+
var podfileRegex = new Regex(
214+
$@"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', '(.+)'\nend\n"
215+
);
216+
217+
if (!podfileRegex.IsMatch(podfile))
218+
podfile += requiredTarget;
219+
else
220+
{
221+
var podfileTarget = podfileRegex.Match(podfile).ToString();
222+
podfile = podfile.Replace(podfileTarget, requiredTarget);
223+
}
224+
165225
File.WriteAllText(podfilePath, podfile);
166226
}
167227

228+
static string ResolveOneSignalXCFrameworkVersion()
229+
{
230+
var dependenciesFilePath = Path.Combine(
231+
"Packages",
232+
"com.onesignal.unity.ios",
233+
"Editor",
234+
"OneSignaliOSDependencies.xml"
235+
);
236+
237+
if (!File.Exists(dependenciesFilePath))
238+
{
239+
Debug.LogWarning(
240+
$"Could not find {dependenciesFilePath}; falling back to default OneSignalXCFramework version range."
241+
);
242+
return null;
243+
}
244+
245+
var dependenciesFile = File.ReadAllText(dependenciesFilePath);
246+
var dependenciesRegex = new Regex(
247+
"(?<=<iosPod name=\"OneSignalXCFramework\" version=\")[^\"]+(?=\" addToAllTargets=\"true\" />)"
248+
);
249+
250+
if (!dependenciesRegex.IsMatch(dependenciesFile))
251+
{
252+
Debug.LogWarning(
253+
$"Could not read OneSignalXCFramework version from {dependenciesFilePath}; falling back to default version range."
254+
);
255+
return null;
256+
}
257+
258+
return dependenciesRegex.Match(dependenciesFile).ToString();
259+
}
260+
168261
static void CopyFileOrDirectory(string sourcePath, string destinationPath)
169262
{
170263
var file = new FileInfo(sourcePath);
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#if UNITY_IOS
2+
3+
using System.IO;
4+
using UnityEditor;
5+
using UnityEditor.Build;
6+
using UnityEditor.Build.Reporting;
7+
using UnityEditor.iOS.Xcode;
8+
using UnityEngine;
9+
10+
namespace App.Editor.iOS
11+
{
12+
/// <summary>
13+
/// Final iOS post-processor for the demo app. Runs AFTER the OneSignal
14+
/// SDK and demo widget post-processors so it can correct things they set:
15+
///
16+
/// 1. Flips the main target's aps-environment from "production" (the SDK
17+
/// default) to "development". The demo only ever runs on simulator or
18+
/// a development device; "production" mismatches the simulator's APNS
19+
/// environment and triggers iOS's "Keep receiving notifications?"
20+
/// tuning prompt on first delivery (matches what the Flutter demo
21+
/// ships with).
22+
///
23+
/// 2. Normalizes extension bundle IDs to short suffixes (`.NSE`, `.LA`)
24+
/// to match the Flutter demo and keep provisioning profile names
25+
/// consistent across SDKs.
26+
///
27+
/// 3. Pins DEVELOPMENT_TEAM on all targets so a future Manual signing
28+
/// setup with the OneSignal-owned profiles works without manual
29+
/// fix-up in Xcode.
30+
/// </summary>
31+
public class SigningPostProcessor : IPostprocessBuildWithReport
32+
{
33+
private const string AppleTeamId = "99SW8E36CT";
34+
private const string ApsEnvironment = "development";
35+
36+
private const string NseTargetName = "OneSignalNotificationServiceExtension";
37+
private const string WidgetTargetName = "OneSignalWidgetExtension";
38+
39+
// Short bundle-id suffixes (match the Flutter demo).
40+
private const string NseBundleSuffix = "NSE";
41+
private const string WidgetBundleSuffix = "LA";
42+
43+
// Run after both demo widget post-processor (45) and SDK
44+
// post-processor (45). 100 puts us after pod install (50) too.
45+
public int callbackOrder => 100;
46+
47+
public void OnPostprocessBuild(BuildReport report)
48+
{
49+
if (report.summary.platform != BuildTarget.iOS)
50+
return;
51+
52+
var outputPath = report.summary.outputPath;
53+
FixupApsEnvironment(outputPath);
54+
FixupSigningAndBundleIds(outputPath);
55+
}
56+
57+
private static void FixupApsEnvironment(string outputPath)
58+
{
59+
var project = new PBXProject();
60+
var projectPath = PBXProject.GetPBXProjectPath(outputPath);
61+
project.ReadFromString(File.ReadAllText(projectPath));
62+
63+
var mainTargetGuid = project.GetUnityMainTargetGuid();
64+
var relPath = project.GetBuildPropertyForAnyConfig(
65+
mainTargetGuid,
66+
"CODE_SIGN_ENTITLEMENTS"
67+
);
68+
69+
if (string.IsNullOrEmpty(relPath))
70+
{
71+
Debug.LogWarning(
72+
"[SigningPostProcessor] Main target has no CODE_SIGN_ENTITLEMENTS; "
73+
+ "skipping aps-environment fixup."
74+
);
75+
return;
76+
}
77+
78+
var fullPath = Path.Combine(outputPath, relPath);
79+
if (!File.Exists(fullPath))
80+
{
81+
Debug.LogWarning(
82+
$"[SigningPostProcessor] Entitlements file not found at {fullPath}; skipping."
83+
);
84+
return;
85+
}
86+
87+
var plist = new PlistDocument();
88+
plist.ReadFromFile(fullPath);
89+
plist.root.SetString("aps-environment", ApsEnvironment);
90+
plist.WriteToFile(fullPath);
91+
92+
Debug.Log(
93+
$"[SigningPostProcessor] Set aps-environment=\"{ApsEnvironment}\" in {relPath}"
94+
);
95+
}
96+
97+
private static void FixupSigningAndBundleIds(string outputPath)
98+
{
99+
var project = new PBXProject();
100+
var projectPath = PBXProject.GetPBXProjectPath(outputPath);
101+
project.ReadFromString(File.ReadAllText(projectPath));
102+
103+
var appId = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.iOS);
104+
105+
ApplyTeamId(project, project.GetUnityMainTargetGuid(), "Unity-iPhone");
106+
107+
ApplyExtensionFixup(
108+
project,
109+
NseTargetName,
110+
$"{appId}.{NseBundleSuffix}"
111+
);
112+
ApplyExtensionFixup(
113+
project,
114+
WidgetTargetName,
115+
$"{appId}.{WidgetBundleSuffix}"
116+
);
117+
118+
File.WriteAllText(projectPath, project.WriteToString());
119+
}
120+
121+
private static void ApplyTeamId(PBXProject project, string targetGuid, string label)
122+
{
123+
if (string.IsNullOrEmpty(targetGuid))
124+
return;
125+
126+
project.SetBuildProperty(targetGuid, "DEVELOPMENT_TEAM", AppleTeamId);
127+
Debug.Log($"[SigningPostProcessor] Pinned DEVELOPMENT_TEAM={AppleTeamId} on {label}");
128+
}
129+
130+
private static void ApplyExtensionFixup(
131+
PBXProject project,
132+
string targetName,
133+
string bundleId
134+
)
135+
{
136+
var guid = project.TargetGuidByName(targetName);
137+
if (string.IsNullOrEmpty(guid))
138+
{
139+
Debug.LogWarning(
140+
$"[SigningPostProcessor] Target '{targetName}' not found; skipping."
141+
);
142+
return;
143+
}
144+
145+
project.SetBuildProperty(guid, "PRODUCT_BUNDLE_IDENTIFIER", bundleId);
146+
ApplyTeamId(project, guid, targetName);
147+
Debug.Log(
148+
$"[SigningPostProcessor] Set {targetName} PRODUCT_BUNDLE_IDENTIFIER={bundleId}"
149+
);
150+
}
151+
}
152+
}
153+
154+
#endif

examples/demo/Assets/App/Editor/iOS/SigningPostProcessor.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
-keep class com.onesignal.** { *; }
22

33
# Work around for IllegalStateException with kotlinx-coroutines-android
4-
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
4+
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
5+
6+
# WorkManager initializes a Room database through AndroidX Startup before Unity starts.
7+
# Unity release builds run R8, so keep the generated database implementation reachable.
8+
-keep class androidx.work.impl.WorkDatabase* { *; }
9+
-keep class androidx.work.impl.model.** { *; }

0 commit comments

Comments
 (0)