diff --git a/Sources/AppcuesKit/Appcues.swift b/Sources/AppcuesKit/Appcues.swift index 398e63188..a7216c5d0 100644 --- a/Sources/AppcuesKit/Appcues.swift +++ b/Sources/AppcuesKit/Appcues.swift @@ -232,6 +232,17 @@ public class Appcues: NSObject { } } + /// Provide the APNs device token to Appcues. + /// + /// - Parameter deviceToken: A globally unique token that identifies this device to APNs. + /// + /// This function is intended to be called from your `UIApplicationDelegate`'s + /// `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)` function: + @objc + public func setPushToken(_ deviceToken: Data?) { + storage.pushToken = deviceToken?.map { String(format: "%02x", $0) }.joined() + } + /// Register a trait that modifies an `Experience`. /// - Parameter trait: Trait to register. /// - Returns: Whether the trait was successfully registered. diff --git a/Sources/AppcuesKit/Data/Analytics/AutoPropertyDecorator.swift b/Sources/AppcuesKit/Data/Analytics/AutoPropertyDecorator.swift index eb57cc258..4ec7d0539 100644 --- a/Sources/AppcuesKit/Data/Analytics/AutoPropertyDecorator.swift +++ b/Sources/AppcuesKit/Data/Analytics/AutoPropertyDecorator.swift @@ -133,16 +133,23 @@ internal class AutoPropertyDecorator: AnalyticsDecorating { } private func deviceAutoProperties() -> [String: Any] { - [ + // Explicitly encode null value + let pushToken: Any = storage.pushToken ?? NSNull() + + var properties = [ "_deviceId": storage.deviceID, - "_language": deviceLanguage, + "_pushToken": pushToken, // TODO: more properties - // _pushToken // _pushSubscriptionStatus // _pushEnabled // _pushEnabledBackground ] - .compactMapValues { $0 } + + if let language = deviceLanguage { + properties["_language"] = language + } + + return properties } } diff --git a/Sources/AppcuesKit/Data/Networking/DynamicCodingKeys.swift b/Sources/AppcuesKit/Data/Networking/DynamicCodingKeys.swift index 0b00cb225..53dfe1283 100644 --- a/Sources/AppcuesKit/Data/Networking/DynamicCodingKeys.swift +++ b/Sources/AppcuesKit/Data/Networking/DynamicCodingKeys.swift @@ -63,6 +63,8 @@ extension KeyedEncodingContainer where K == DynamicCodingKeys { try self.encode(bool, forKey: codingKey) case let date as Date: try self.encode(date, forKey: codingKey) + case is NSNull: + try self.encodeNil(forKey: codingKey) default: encodingErrorKeys.append(codingKey.stringValue) } diff --git a/Sources/AppcuesKit/Data/Storage.swift b/Sources/AppcuesKit/Data/Storage.swift index a7d1316e3..91fbe35cd 100644 --- a/Sources/AppcuesKit/Data/Storage.swift +++ b/Sources/AppcuesKit/Data/Storage.swift @@ -26,6 +26,9 @@ internal protocol DataStoring: AnyObject { /// Optional, base 64 encoded signature to use as bearer token on API requests from the current user var userSignature: String? { get set } + + /// Push token for APNs + var pushToken: String? { get set } } internal class Storage: DataStoring { @@ -37,6 +40,7 @@ internal class Storage: DataStoring { case lastContentShownAt case groupID case userSignature + case pushToken } private let config: Appcues.Config @@ -99,6 +103,15 @@ internal class Storage: DataStoring { } } + internal var pushToken: String? { + get { + return read(.pushToken, defaultValue: nil) + } + set { + write(.pushToken, newValue: newValue) + } + } + init(container: DIContainer) { self.config = container.resolve(Appcues.Config.self) self.deviceID = UIDevice.identifier