Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
44c3756
chore(demo): prepare demo app for Appium E2E tests
fadi-george Apr 28, 2026
99a80dd
chore(demo): remove LogManager, use Debug.Log
fadi-george Apr 28, 2026
207de0d
chore(demo): standardize UI element names for Appium tests
fadi-george Apr 28, 2026
b225f34
chore(demo): replace loading overlay with inline loading states
fadi-george Apr 28, 2026
5d10a9a
chore(demo): improve UI element names for Appium tests
fadi-george Apr 28, 2026
5f59a7d
feat(demo): add accessibility bridge for Appium E2E
fadi-george Apr 28, 2026
ce4aaef
fix(demo): improve a11y bridge reliability
fadi-george May 1, 2026
976475e
fix(demo): force iOS keyboard dismiss on modal close
fadi-george May 1, 2026
44978e3
fix(demo): fix loading state in login/logout flow
fadi-george May 1, 2026
185d13d
fix(demo): fix info-icon taps in Appium E2E tests
fadi-george May 2, 2026
bd65c0b
fix(demo): add a11y support for SwitchToggle
fadi-george May 2, 2026
ba1e616
feat(demo): add iOS signing post-processor
fadi-george May 2, 2026
dd0d6de
fix(demo): source IAM paused state from prefs
fadi-george May 2, 2026
fad8cf5
fix(demo): clarify add button labels
fadi-george May 2, 2026
66d364e
fix(demo): align dialog to top with padding
fadi-george May 2, 2026
45acb10
fix(demo): add a11y label to toast for Appium
fadi-george May 2, 2026
cbd0970
wip
fadi-george May 2, 2026
cd675ec
fix(demo): remove bottom safe area padding
fadi-george May 2, 2026
e166afb
fix(demo): stabilize XCUITest taps on ScrollView
fadi-george May 2, 2026
90cb3b2
fix(demo): add named-tap fallback for E2E reliability
fadi-george May 4, 2026
89178bb
fix(demo): refactor E2E tap fallback to use target registry
fadi-george May 5, 2026
f6feee1
fix(demo): prevent double-submit and duplicate aliases
fadi-george May 6, 2026
144d88b
refactor(demo): consolidate upsert helpers into MergePairs
fadi-george May 6, 2026
5db7c85
feat(demo): add app bar shadow element
fadi-george May 6, 2026
73517f2
chore(demo): bump Unity editor and packages
fadi-george May 11, 2026
15eac3f
fix(demo): trigger E2E tap on pointer up
fadi-george May 12, 2026
0119424
feat(demo): sync accessibility on toast show/hide
fadi-george May 12, 2026
24c5076
fix(demo): use prefs for location shared state
fadi-george May 12, 2026
386b6de
fix(demo): use cached location shared state
fadi-george May 12, 2026
e5f7ac8
fix(demo): cancel E2E tap on click event
fadi-george May 12, 2026
459c708
feat(demo): add Android native accessibility bridge
fadi-george May 12, 2026
a9aa1ad
fix(demo): hide default checkbox indicator
fadi-george May 12, 2026
3654d95
fix(demo): suppress spell-check underline on E2E input
fadi-george May 12, 2026
f3832a0
feat(demo): add E2E gutter swipe scroll support
fadi-george May 12, 2026
a712965
fix(demo): skip a11y sync when nothing changed
fadi-george May 13, 2026
152d6da
fix(demo): use BaseBoolField for radio a11y support
fadi-george May 13, 2026
d119746
fix(demo): incremental a11y rebuild to preserve node identity
fadi-george May 13, 2026
acbfa62
fix(demo): pin widget pod to OneSignal version
fadi-george May 13, 2026
5f0312e
fix(demo): register SDK listeners before config
fadi-george May 13, 2026
9270923
refactor(demo): remove E2E tap fallback machinery
fadi-george May 13, 2026
71f3fc4
refactor(demo): rename E2E tap registry to AndroidClickTarget
fadi-george May 13, 2026
d163e13
fix(demo): set simulator arch to arm64 for Apple Silicon
fadi-george May 13, 2026
e16974f
refactor(demo): replace root PointerDown with ClickEvent
fadi-george May 13, 2026
d6cbdc5
fix(demo): restore panel-root PointerDown for iOS info icons
fadi-george May 15, 2026
ec0d7df
refactor(demo): disable tap-marker overlay
fadi-george May 15, 2026
ef04560
refactor(demo): remove ShowToast calls
fadi-george May 15, 2026
74a1c22
refactor(demo): trim inputs and extract RenderPairList
fadi-george May 15, 2026
3f8bb50
fix(demo): suppress dev console in E2E mode
fadi-george May 15, 2026
f7a94f6
fix(demo): force accessibility refresh on foreground
fadi-george May 15, 2026
c204543
fix: [SDK-4406] use monotonic row index in MultiPairInputDialog
fadi-george May 15, 2026
f8ba425
fix(demo): fix stale Android click targets and IAM WebView debugging
fadi-george May 16, 2026
c35fb86
fix(demo): address Unity demo PR feedback
fadi-george May 16, 2026
a213803
fix(demo): register back button as E2E tap target
fadi-george May 16, 2026
384badd
fix(demo): apply safe area to secondary screen
fadi-george May 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
-keep class com.onesignal.** { *; }

# Work around for IllegalStateException with kotlinx-coroutines-android
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}

# WorkManager initializes a Room database through AndroidX Startup before Unity starts.
# Unity release builds run R8, so keep the generated database implementation reachable.
-keep class androidx.work.impl.WorkDatabase* { *; }
-keep class androidx.work.impl.model.** { *; }
3 changes: 3 additions & 0 deletions examples/demo/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Default App ID (used when ONESIGNAL_APP_ID is empty or missing): 77e32082-ea27-42e3-a898-c72e141824ef
ONESIGNAL_APP_ID=your-onesignal-app-id
ONESIGNAL_API_KEY=your_rest_api_key
E2E_MODE=false
56 changes: 54 additions & 2 deletions examples/demo/Assets/App/Editor/iOS/BuildPostProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using UnityEditor.iOS.Xcode.Extensions;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace App.Editor.iOS
{
Expand Down Expand Up @@ -159,12 +160,63 @@ static void AddWidgetExtensionToPodFile(string outputPath)
return;
}

// Keep the widget extension pinned to the same OneSignalXCFramework version as the
// core plugin so CocoaPods can resolve a single shared version across targets.
var requiredVersion = ResolveOneSignalXCFrameworkVersion();
var versionConstraint =
requiredVersion != null ? $"'{requiredVersion}'" : "'>= 5.0.2', '< 6.0.0'";
var requiredTarget =
$"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', {versionConstraint}\nend\n";

var podfile = File.ReadAllText(podfilePath);
podfile +=
$"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', '>= 5.0.2', '< 6.0.0'\nend\n";
var podfileRegex = new Regex(
$@"target '{WidgetExtensionTargetName}' do\n pod 'OneSignalXCFramework', '(.+)'\nend\n"
);

if (!podfileRegex.IsMatch(podfile))
podfile += requiredTarget;
else
{
var podfileTarget = podfileRegex.Match(podfile).ToString();
podfile = podfile.Replace(podfileTarget, requiredTarget);
}

File.WriteAllText(podfilePath, podfile);
}

static string ResolveOneSignalXCFrameworkVersion()
{
var dependenciesFilePath = Path.Combine(
"Packages",
"com.onesignal.unity.ios",
"Editor",
"OneSignaliOSDependencies.xml"
);

if (!File.Exists(dependenciesFilePath))
{
Debug.LogWarning(
$"Could not find {dependenciesFilePath}; falling back to default OneSignalXCFramework version range."
);
return null;
}

var dependenciesFile = File.ReadAllText(dependenciesFilePath);
var dependenciesRegex = new Regex(
"(?<=<iosPod name=\"OneSignalXCFramework\" version=\")[^\"]+(?=\" addToAllTargets=\"true\" />)"
);

if (!dependenciesRegex.IsMatch(dependenciesFile))
{
Debug.LogWarning(
Comment thread
fadi-george marked this conversation as resolved.
$"Could not read OneSignalXCFramework version from {dependenciesFilePath}; falling back to default version range."
);
return null;
}

return dependenciesRegex.Match(dependenciesFile).ToString();
}

static void CopyFileOrDirectory(string sourcePath, string destinationPath)
{
var file = new FileInfo(sourcePath);
Expand Down
154 changes: 154 additions & 0 deletions examples/demo/Assets/App/Editor/iOS/SigningPostProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#if UNITY_IOS

using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.iOS.Xcode;
using UnityEngine;

namespace App.Editor.iOS
{
/// <summary>
/// Final iOS post-processor for the demo app. Runs AFTER the OneSignal
/// SDK and demo widget post-processors so it can correct things they set:
///
/// 1. Flips the main target's aps-environment from "production" (the SDK
/// default) to "development". The demo only ever runs on simulator or
/// a development device; "production" mismatches the simulator's APNS
/// environment and triggers iOS's "Keep receiving notifications?"
/// tuning prompt on first delivery (matches what the Flutter demo
/// ships with).
///
/// 2. Normalizes extension bundle IDs to short suffixes (`.NSE`, `.LA`)
/// to match the Flutter demo and keep provisioning profile names
/// consistent across SDKs.
///
/// 3. Pins DEVELOPMENT_TEAM on all targets so a future Manual signing
/// setup with the OneSignal-owned profiles works without manual
/// fix-up in Xcode.
/// </summary>
public class SigningPostProcessor : IPostprocessBuildWithReport
{
private const string AppleTeamId = "99SW8E36CT";
private const string ApsEnvironment = "development";

private const string NseTargetName = "OneSignalNotificationServiceExtension";
private const string WidgetTargetName = "OneSignalWidget";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 🔴 SigningPostProcessor.WidgetTargetName is set to "OneSignalWidget", but BuildPostProcessor.AddAppExtension registers the widget under "OneSignalWidgetExtension" (BuildPostProcessor.cs:48, :128). project.TargetGuidByName("OneSignalWidget") returns null, ApplyExtensionFixup logs the "Target not found" warning and returns, so PRODUCT_BUNDLE_IDENTIFIER is never rewritten to {appId}.LA and DEVELOPMENT_TEAM is never pinned. The widget keeps its default bundle ID com.onesignal.example.OneSignalWidgetExtension, which mismatches the new iOS/ExportOptions.plist mapping for com.onesignal.example.LA → "Appium Demo - Live Activity", so xcodebuild -exportArchive will fail to find a matching provisioning profile. Fix: private const string WidgetTargetName = "OneSignalWidgetExtension";.

Extended reasoning...

What the bug is

examples/demo/Assets/App/Editor/iOS/SigningPostProcessor.cs:37 declares:

private const string WidgetTargetName = "OneSignalWidget";

That string is the widget extension's folder/relative path (WidgetExtensionTargetRelativePath in BuildPostProcessor.cs:46), not the Xcode target name. The Xcode target itself is registered with a different name.

The specific code path that triggers it

BuildPostProcessor.cs:48 defines WidgetExtensionTargetName = "OneSignalWidgetExtension" and BuildPostProcessor.cs:128 calls:

extensionGuid = project.AddAppExtension(
    project.GetUnityMainTargetGuid(),
    WidgetExtensionTargetName,                                  // "OneSignalWidgetExtension"
    $"{PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.iOS)}.{WidgetExtensionTargetName}",
    $"{WidgetExtensionTargetRelativePath}/Info.plist"
);

So the PBX target is literally named OneSignalWidgetExtension with a default bundle ID of {appId}.OneSignalWidgetExtension.

SigningPostProcessor.ApplyExtensionFixup (lines 130–147) then calls project.TargetGuidByName("OneSignalWidget"). No such target exists, so guid is null/empty and the function hits its early-return:

if (string.IsNullOrEmpty(guid))
{
    Debug.LogWarning($"[SigningPostProcessor] Target '{targetName}' not found; skipping.");
    return;
}

Neither SetBuildProperty(guid, "PRODUCT_BUNDLE_IDENTIFIER", bundleId) nor ApplyTeamId(...) runs for the widget target.

Why existing code doesn't prevent it

There is no callsite that resolves the target by file path; TargetGuidByName requires the literal PBX target name. The sibling NseTargetName constant ("OneSignalNotificationServiceExtension") happens to match the SDK's NSE target name, masking the inconsistency for the NSE case. Only the widget constant is wrong.

Step-by-step proof

  1. Unity build emits the Xcode project. BuildPostProcessor (callbackOrder 45) runs first, calls AddAppExtension(..., "OneSignalWidgetExtension", ...). PBX now contains a target named OneSignalWidgetExtension with bundle ID com.onesignal.example.OneSignalWidgetExtension.
  2. SigningPostProcessor (callbackOrder 100) runs. FixupSigningAndBundleIds reads the PBXProject, then invokes ApplyExtensionFixup(project, "OneSignalWidget", "com.onesignal.example.LA").
  3. project.TargetGuidByName("OneSignalWidget") → null. The IsNullOrEmpty branch fires, logs [SigningPostProcessor] Target 'OneSignalWidget' not found; skipping., and returns.
  4. PRODUCT_BUNDLE_IDENTIFIER on the widget target is never rewritten and DEVELOPMENT_TEAM is never pinned. The widget retains com.onesignal.example.OneSignalWidgetExtension.
  5. CI runs xcodebuild -exportArchive -exportOptionsPlist iOS/ExportOptions.plist. ExportOptions.plist declares signingStyle = manual with provisioningProfiles keyed by com.onesignal.example.LA → "Appium Demo - Live Activity". xcodebuild walks the archive's targets, finds the widget's actual bundle ID com.onesignal.example.OneSignalWidgetExtension, looks up that key in the dictionary, finds nothing, and fails with No profile for bundle identifier 'com.onesignal.example.OneSignalWidgetExtension'.

Impact

CI/build-time regression introduced by this PR. The PR description explicitly lists "Normalizes extension bundle IDs to short suffixes (.NSE, .LA)" and adds iOS/ExportOptions.plist keyed by those suffixes; the misnamed constant defeats both. NSE works because its constant happens to match; only the widget is broken.

Fix

One-line change at SigningPostProcessor.cs:37:

private const string WidgetTargetName = "OneSignalWidgetExtension";


// Short bundle-id suffixes (match the Flutter demo).
private const string NseBundleSuffix = "NSE";
private const string WidgetBundleSuffix = "LA";

// Run after both demo widget post-processor (45) and SDK
// post-processor (45). 100 puts us after pod install (50) too.
public int callbackOrder => 100;

public void OnPostprocessBuild(BuildReport report)
{
if (report.summary.platform != BuildTarget.iOS)
return;

var outputPath = report.summary.outputPath;
FixupApsEnvironment(outputPath);
FixupSigningAndBundleIds(outputPath);
}

private static void FixupApsEnvironment(string outputPath)
{
var project = new PBXProject();
var projectPath = PBXProject.GetPBXProjectPath(outputPath);
project.ReadFromString(File.ReadAllText(projectPath));

var mainTargetGuid = project.GetUnityMainTargetGuid();
var relPath = project.GetBuildPropertyForAnyConfig(
mainTargetGuid,
"CODE_SIGN_ENTITLEMENTS"
);

if (string.IsNullOrEmpty(relPath))
{
Debug.LogWarning(
"[SigningPostProcessor] Main target has no CODE_SIGN_ENTITLEMENTS; "
+ "skipping aps-environment fixup."
);
return;
}

var fullPath = Path.Combine(outputPath, relPath);
if (!File.Exists(fullPath))
{
Debug.LogWarning(
$"[SigningPostProcessor] Entitlements file not found at {fullPath}; skipping."
);
return;
}

var plist = new PlistDocument();
plist.ReadFromFile(fullPath);
plist.root.SetString("aps-environment", ApsEnvironment);
plist.WriteToFile(fullPath);

Debug.Log(
$"[SigningPostProcessor] Set aps-environment=\"{ApsEnvironment}\" in {relPath}"
);
}

private static void FixupSigningAndBundleIds(string outputPath)
{
var project = new PBXProject();
var projectPath = PBXProject.GetPBXProjectPath(outputPath);
project.ReadFromString(File.ReadAllText(projectPath));

var appId = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.iOS);

ApplyTeamId(project, project.GetUnityMainTargetGuid(), "Unity-iPhone");

ApplyExtensionFixup(
project,
NseTargetName,
$"{appId}.{NseBundleSuffix}"
);
ApplyExtensionFixup(
project,
WidgetTargetName,
$"{appId}.{WidgetBundleSuffix}"
);

File.WriteAllText(projectPath, project.WriteToString());
}

private static void ApplyTeamId(PBXProject project, string targetGuid, string label)
{
if (string.IsNullOrEmpty(targetGuid))
return;

project.SetBuildProperty(targetGuid, "DEVELOPMENT_TEAM", AppleTeamId);
Debug.Log($"[SigningPostProcessor] Pinned DEVELOPMENT_TEAM={AppleTeamId} on {label}");
}

private static void ApplyExtensionFixup(
PBXProject project,
string targetName,
string bundleId
)
{
var guid = project.TargetGuidByName(targetName);
if (string.IsNullOrEmpty(guid))
{
Debug.LogWarning(
$"[SigningPostProcessor] Target '{targetName}' not found; skipping."
);
return;
}

project.SetBuildProperty(guid, "PRODUCT_BUNDLE_IDENTIFIER", bundleId);
ApplyTeamId(project, guid, targetName);
Debug.Log(
$"[SigningPostProcessor] Set {targetName} PRODUCT_BUNDLE_IDENTIFIER={bundleId}"
);
}
}
}

#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
-keep class com.onesignal.** { *; }

# Work around for IllegalStateException with kotlinx-coroutines-android
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}

# WorkManager initializes a Room database through AndroidX Startup before Unity starts.
# Unity release builds run R8, so keep the generated database implementation reachable.
-keep class androidx.work.impl.WorkDatabase* { *; }
-keep class androidx.work.impl.model.** { *; }
Loading