urbanairship / ios-library

Urban Airship iOS SDK
http://urbanairship.com
Apache License 2.0
478 stars 265 forks source link

Configuration's enabled features overridden by previous privacyManager.enableFeature call #396

Closed SwiftNativeDeveloper closed 7 months ago

SwiftNativeDeveloper commented 8 months ago

Preliminary Info

What Airship dependencies are you using?

AirshipCore, 17.8.0

What are the versions of any relevant development tools you are using?

Xcode 15.x

Report

What unexpected behavior are you seeing?

Airship push is calling application registerForRemoteNotifications even though the configuration at takeoff has enabled features set to [ ].

Looking at the code, the privacy manager is restoring the previously launched app's settings and disregarding what is provided at takeoff.

What is the expected behavior?

AirshipConfiguration specifying enabled features as [] should be respected even if previous run had enabled something via privacy manager. Or documentation should be updated.

The docs in the AirshipConfiguration features states:

/// Default enabled Airship features for the app. For more details, see `PrivacyManager`.
/// Defaults to `all`.
public var enabledFeatures: AirshipFeature = .all

AirshipPrivacyManager states:

/// The privacy manager allow enabling/disabling features in the SDK.
/// The SDK will not make any network requests or collect data if all features our disabled, with
/// a few exceptions when going from enabled -> disabled. To have the SDK opt-out of all features on startup,
/// set the default enabled features in the Config to an empty option set, or in the
/// airshipconfig.plist file with `enabledFeatures = none`.
/// If any feature is enabled, the SDK will collect and send the following data:
/// - Channel ID
/// - Locale
/// - TimeZone
/// - Platform
/// - Opt in state (push and notifications)
/// - SDK version
/// - Accengage Device ID (Accengage module for migration)
@objc(UAPrivacyManager)
public final class AirshipPrivacyManager: NSObject, Sendable {

What takes precedence? It appears that AirshipPrivacyManager and previous app launches are persisting in a cache and restored, and that the configuration applied during takeOff isn't being respected. This could be by design, but from the docs it isn't clear what the expected behavior is.

If behaves as designed, the docs for privacy manager should state something like 'first time'. Many of the Google Firebase products describe the order of precedence when you can enable or disable things via .plist, launch argument, and enable/disable function calls.

Additional documentation updates, if behaves as designed, would be to articulate the enablement persists for app launches until disabled, irrespective of the configuration enabled features.

Does Airship have docs for this?

What are the steps to reproduce the unexpected behavior?

  1. Launch configurations with everything disabled, empty feature set []. configuration.enabledFeatures = []

  2. Later, enable the push feature when the user triggers a workflow in the UI. Airship.shared.privacyManager.enableFeatures([.push, .tagsAndAttributes])

  3. Verify that this kicks off a registration request and successful callback with UIApplication and delegate Output of this log: AirshipLogger.info("Device token string: \(tokenString)")

  4. Launch the app a subsequent time with all features disabled via the configuration configuration.enabledFeatures = []

  5. Expect airship to NOT establish push feature, but it does. Output of this log: AirshipLogger.info("Device token string: \(tokenString)")

Do you have logging for the issue?

Available upon request. Evident in the logs that the device token is being received in application delegate when configuration is set to empty.

crow commented 7 months ago

The configuration sets the defaults the app starts with but is overridden by the setters on the privacy manager instance as you've noticed. In general, the configuration is more static and intended for setting defaults that can be overwritten. We can definitely make this tradeoff more clear in documentation. Thanks for reporting.

SwiftNativeDeveloper commented 7 months ago

@crow

Since I can't call the Privacy Manager before takeoff, what would the recommended approach be if I want to control what features are enabled each launch?

Among many things, I want to prevent an unexpected call to application.registerForRemoteNotifications. Privacy manager restoring push as enabled is I triggering code paths to invoke it.

I could perhaps disable privacy manager .push feature immediately after takeoff but that makes assumptions about the creation order, threading, asynchronicity, etc. A more explicit mechanism would, however, be preferred.

crow commented 7 months ago

We'll likely need to add a configuration flag to control this resetting behavior since overriding via the config doesn't suit most use cases. As you seem to have alluded to by mentioning assumptions - disabling push immediately after takeoff via the privacy manager doesn't occur in time to prevent the registration call.

crow commented 7 months ago

Just released 17.10.0 which includes a new flag resetEnabledFeatures which will reset enabled features to the set defined in the AirshipConfig.plist on each take off. Thanks for reporting!

SwiftNativeDeveloper commented 7 months ago

@crow which branch was the change made on? Will the change be cherry picked and applied to 18.x?