Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 13 additions & 9 deletions examples/demo/App.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@
E194A3F19072CB17A8F1A12E /* SmsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmsSection.swift; sourceTree = "<group>"; };
ECAC7EF0B67920F9FEC4F129 /* TagsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsSection.swift; sourceTree = "<group>"; };
F46DFACB9F304B9374F3C570 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
"TEMP_28AA92EA-E2C8-4969-80AB-657A0AF14B2F" /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = "<group>"; };
"TEMP_0E1FE525-4849-45DE-A712-899A0C859B23" /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -567,10 +567,10 @@
path = Views;
sourceTree = "<group>";
};
"TEMP_D8085C8F-3CA5-457D-9FB3-7C8E9DA8B66A" /* demo */ = {
"TEMP_99839FD6-FA1F-4A7A-8510-1B74D1DAA81F" /* demo */ = {
isa = PBXGroup;
children = (
"TEMP_28AA92EA-E2C8-4969-80AB-657A0AF14B2F" /* Build.xcconfig */,
"TEMP_0E1FE525-4849-45DE-A712-899A0C859B23" /* Build.xcconfig */,
);
path = demo;
sourceTree = "<group>";
Expand Down Expand Up @@ -940,7 +940,7 @@
/* Begin XCBuildConfiguration section */
0D2EF3911CA89837C30DB0D1 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = "TEMP_28AA92EA-E2C8-4969-80AB-657A0AF14B2F" /* Build.xcconfig */;
baseConfigurationReference = "TEMP_0E1FE525-4849-45DE-A712-899A0C859B23" /* Build.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
Expand All @@ -958,7 +958,7 @@
};
4A0C935808978B5A7673E412 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = "TEMP_28AA92EA-E2C8-4969-80AB-657A0AF14B2F" /* Build.xcconfig */;
baseConfigurationReference = "TEMP_0E1FE525-4849-45DE-A712-899A0C859B23" /* Build.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App.entitlements;
Expand Down Expand Up @@ -1012,6 +1012,7 @@
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = "";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand All @@ -1030,6 +1031,7 @@
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTS_MACCATALYST = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.9;
Expand All @@ -1038,7 +1040,7 @@
};
D0E56A85F1C385808720F94B /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = "TEMP_28AA92EA-E2C8-4969-80AB-657A0AF14B2F" /* Build.xcconfig */;
baseConfigurationReference = "TEMP_0E1FE525-4849-45DE-A712-899A0C859B23" /* Build.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
Expand All @@ -1060,7 +1062,7 @@
};
EB1CC3A930E09FEBECF9195D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = "TEMP_28AA92EA-E2C8-4969-80AB-657A0AF14B2F" /* Build.xcconfig */;
baseConfigurationReference = "TEMP_0E1FE525-4849-45DE-A712-899A0C859B23" /* Build.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App.entitlements;
Expand All @@ -1083,7 +1085,7 @@
};
F305A3E63851EE49DA2D190E /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = "TEMP_28AA92EA-E2C8-4969-80AB-657A0AF14B2F" /* Build.xcconfig */;
baseConfigurationReference = "TEMP_0E1FE525-4849-45DE-A712-899A0C859B23" /* Build.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
Expand All @@ -1106,7 +1108,7 @@
};
F5FD25168D9B32A08A468069 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = "TEMP_28AA92EA-E2C8-4969-80AB-657A0AF14B2F" /* Build.xcconfig */;
baseConfigurationReference = "TEMP_0E1FE525-4849-45DE-A712-899A0C859B23" /* Build.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements;
DEVELOPMENT_TEAM = 99SW8E36CT;
Expand Down Expand Up @@ -1158,6 +1160,7 @@
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = "";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
Expand All @@ -1183,6 +1186,7 @@
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.9;
Expand Down
6 changes: 6 additions & 0 deletions examples/demo/App/Services/LiveActivityController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@

import Foundation
import OneSignalFramework
#if !targetEnvironment(macCatalyst)
import OneSignalLiveActivities
#endif

/// Order tracking phases used by the Live Activity demo
enum LiveActivityStatus: String, CaseIterable, Identifiable {
Expand Down Expand Up @@ -76,7 +78,9 @@ enum LiveActivityController {

@available(iOS 16.1, *)
static func setup() {
#if !targetEnvironment(macCatalyst)
OneSignal.LiveActivities.setupDefault()
#endif
}

@available(iOS 16.1, *)
Expand All @@ -85,6 +89,7 @@ enum LiveActivityController {
orderNumber: String,
status: LiveActivityStatus
) {
#if !targetEnvironment(macCatalyst)
let attributes: [String: Any] = [
"orderNumber": orderNumber
]
Expand All @@ -98,6 +103,7 @@ enum LiveActivityController {
attributes: attributes,
content: content
)
#endif
Comment on lines 92 to +106

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Act on (A/B/C/D): Only setup/start are compiled out. LiveActivitySection still mounts on Catalyst (ContentView), Start silently no-ops, and update/end below still hit the Live Activities REST API for an activity that cannot exist locally.

Hide/disable the section on Catalyst (mirror the widget’s “unavailable” copy), and gate all controller operations consistently.

}

static func update(appId: String, activityId: String, status: LiveActivityStatus) async -> Bool {
Expand Down
4 changes: 4 additions & 0 deletions examples/demo/OneSignalWidget/OneSignalWidgetBundle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ import SwiftUI
@main
struct OneSignalWidgetBundle: WidgetBundle {
var body: some Widget {
#if targetEnvironment(macCatalyst)
OneSignalWidgetPlaceholder()
#else
if #available(iOS 16.2, *) {
OneSignalWidgetLiveActivity()
}
#endif
}
}
42 changes: 41 additions & 1 deletion examples/demo/OneSignalWidget/OneSignalWidgetLiveActivity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,48 @@
* THE SOFTWARE.
*/

import ActivityKit
import WidgetKit
import SwiftUI

#if targetEnvironment(macCatalyst)
private struct CatalystPlaceholderEntry: TimelineEntry {
let date: Date
}

private struct CatalystPlaceholderProvider: TimelineProvider {
func placeholder(in context: Context) -> CatalystPlaceholderEntry {
CatalystPlaceholderEntry(date: Date())
}

func getSnapshot(
in context: Context,
completion: @escaping (CatalystPlaceholderEntry) -> Void
) {
completion(CatalystPlaceholderEntry(date: Date()))
}

func getTimeline(
in context: Context,
completion: @escaping (Timeline<CatalystPlaceholderEntry>) -> Void
) {
completion(Timeline(entries: [CatalystPlaceholderEntry(date: Date())], policy: .never))
}
}

struct OneSignalWidgetPlaceholder: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.onesignal.example.catalyst-placeholder",
provider: CatalystPlaceholderProvider()
) { _ in
Text("OneSignal")
}
.configurationDisplayName("OneSignal")
.description("Live Activities are unavailable on Mac Catalyst.")
}
}
#else
import ActivityKit
import OneSignalLiveActivities

/// Live Activity widget that renders the order tracking flow used by the demo.
Expand Down Expand Up @@ -173,3 +212,4 @@ struct DeliveryProgressBar: View {
.frame(height: 6)
}
}
#endif
2 changes: 2 additions & 0 deletions examples/demo/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ settings:
CODE_SIGN_STYLE: Automatic
DEVELOPMENT_TEAM: ""
GENERATE_INFOPLIST_FILE: NO
SUPPORTS_MACCATALYST: YES
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER: YES
Comment on lines +21 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Act on (A/B/C/D): With DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES and no OneSignal_app_groups_key in Info.plist, OneSignalUserDefaults.appGroupName() becomes group.maccatalyst.com.onesignal.example.onesignal, but entitlements only declare group.com.onesignal.example.onesignal.

That breaks the shared App Group / NSE path this PR is meant to validate, and matches the “macOS App Group authorization prompt” note in the PR body.

Pin OneSignal_app_groups_key on App + NSE, or set derive to NO so the Catalyst bundle id stays com.onesignal.example.

projectReferences:
OneSignalSDK:
path: ../../iOS_SDK/OneSignalSDK/OneSignal.xcodeproj
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,45 @@ public final class OSResilientStorage: NSObject {
/// the NSE can read the same file. Falls back to the app's private
/// Application Support directory when no App Group is entitled.
private static func fileURL() -> URL? {
let fm = FileManager.default
let fileManager = FileManager.default

let groupName = OneSignalUserDefaults.appGroupName()
if let container = fm.containerURL(forSecurityApplicationGroupIdentifier: groupName) {
return container.appendingPathComponent(fileName)
if let container = fileManager.containerURL(forSecurityApplicationGroupIdentifier: groupName) {
do {
return try preparedFileURL(in: container, fileManager: fileManager)
} catch {
OneSignalLog.onesignalLog(
.LL_WARN,
message: "OSResilientStorage could not prepare the App Group container: \(error)"
)
}
Comment on lines +65 to +73

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Act on (A/C/D): After a non-nil App Group containerURL, prepare failure now warns and falls through to Application Support.

Previously the group URL was always returned and a write failure stayed on that path. Silent private rehoming breaks the documented app↔NSE single-file contract and can make Catalyst storage checks look green without touching the entitled group.

Once a container URL is selected, fail closed (return nil / keep the group URL) instead of switching backing stores mid-flight.

}

do {
let support = try fm.url(
let support = try fileManager.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
return support.appendingPathComponent(fileName)
return try preparedFileURL(in: support, fileManager: fileManager)
} catch {
OneSignalLog.onesignalLog(.LL_ERROR, message: "OSResilientStorage could not resolve a container URL: \(error)")
return nil
}
}

static func preparedFileURL(
in directory: URL,
fileManager: FileManager = .default
) throws -> URL {
try fileManager.createDirectory(
at: directory,
withIntermediateDirectories: true
)
return directory.appendingPathComponent(fileName)
}

/// Reads the cache file. Caller is responsible for queue-serialization.
/// Returns an empty dict if the file is missing or unreadable.
private static func loadUnsafe() -> [String: String] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ final class OSResilientStorageTests: XCTestCase {
XCTAssertNil(OSResilientStorage.string(forKey: "never_written_\(UUID().uuidString)"))
}

func testPreparedFileURL_createsMissingContainerForAtomicWrites() throws {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString, isDirectory: true)
let container = root.appendingPathComponent("group.example", isDirectory: true)
defer { try? FileManager.default.removeItem(at: root) }

let fileURL = try OSResilientStorage.preparedFileURL(in: container)
try Data("value".utf8).write(to: fileURL, options: .atomic)

XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path))
Comment on lines +67 to +76

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Consider (A/C/D): This asserts FileManager.createDirectory + atomic Data.write on a temp path. It does not cover the new risky fileURL() behavior (prepare-failure fallthrough / group vs Application Support choice), which is what motivated the production change.

Cover prepare-failure semantics (no private fallback once a group URL was selected), or drop the helper-only assertion.

}

func testSetWithNil_removesKey() {
OSResilientStorage.setString("alpha", forKey: keyA)
XCTAssertEqual(OSResilientStorage.string(forKey: keyA), "alpha")
Expand Down
9 changes: 9 additions & 0 deletions iOS_SDK/OneSignalSDK/Source/OneSignal.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
*/

#import <stdatomic.h>
#import <TargetConditionals.h>
#import "OneSignalFramework.h"
#import <OneSignalOSCore/OneSignalOSCore-Swift.h>
#import "OneSignalInternal.h"
Expand Down Expand Up @@ -206,13 +207,17 @@ + (void)logout {
}

+ (Class<OSLiveActivities>)LiveActivities {
#if TARGET_OS_MACCATALYST
return [OSStubLiveActivities liveActivities];
Comment on lines +210 to +211

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Consider (A/D): OneSignalLiveActivities sources are already blanked under #if targetEnvironment(macCatalyst), so NSClassFromString already misses and the pre-existing else path already returned the stub. These #ifs mainly suppress the facade logs; OSStubLiveActivities still ERROR-logs “module must be added,” which is the wrong diagnosis on Catalyst.

Prefer one platform-aware stub/message owned by the LiveActivities module rather than a third dispatch path in OneSignal.m.

#else
let oneSignalLiveActivities = NSClassFromString(ONE_SIGNAL_LIVE_ACTIVITIES_CLASS_NAME);
if (oneSignalLiveActivities != nil && [oneSignalLiveActivities respondsToSelector:@selector(liveActivities)]) {
return [oneSignalLiveActivities performSelector:@selector(liveActivities)];
} else {
[OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"oneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added."];
return [OSStubLiveActivities liveActivities];
}
#endif
}

+ (Class<OSLocation>)Location {
Expand Down Expand Up @@ -440,12 +445,16 @@ + (void)startUserManager {
}

+ (void)startLiveActivitiesManager {
#if TARGET_OS_MACCATALYST
return;
#else
let oneSignalLiveActivities = NSClassFromString(ONE_SIGNAL_LIVE_ACTIVITIES_CLASS_NAME);
if (oneSignalLiveActivities != nil && [oneSignalLiveActivities respondsToSelector:@selector(start)]) {
[oneSignalLiveActivities performSelector:@selector(start)];
} else {
[OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"oneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added."];
}
#endif
}

+ (void)delayInitializationForPrivacyConsent {
Expand Down
Loading