Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add diagnostics event for Customer Info verification #3823

Merged
Merged
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
8 changes: 7 additions & 1 deletion Sources/Diagnostics/DiagnosticsEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct DiagnosticsEvent: Codable, Equatable {

let version: Int = 1
let eventType: DiagnosticsEvent.EventType
let properties: [String: AnyEncodable]
let properties: [DiagnosticsPropertyKey: AnyEncodable]
let timestamp: Date

enum CodingKeys: String, CodingKey {
Expand All @@ -35,6 +35,12 @@ extension DiagnosticsEvent {

}

enum DiagnosticsPropertyKey: String, Codable {

case verificationResultKey

}

}

extension DiagnosticsEvent {
Expand Down
5 changes: 4 additions & 1 deletion Sources/Diagnostics/DiagnosticsFileHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@

import Foundation

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
protocol DiagnosticsFileHandlerType: Sendable {

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func getEntries() async -> [DiagnosticsEvent]

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func appendEvent(diagnosticsEvent: DiagnosticsEvent) async

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func cleanSentDiagnostics(diagnosticsSentCount: Int) async

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
func emptyDiagnosticsFile() async

}
Expand Down
28 changes: 25 additions & 3 deletions Sources/Diagnostics/DiagnosticsTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,45 @@

import Foundation

@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
protocol DiagnosticsTrackerType {

@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
func track(_ event: DiagnosticsEvent) async

@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
func trackCustomerInfoVerificationResultIfNeeded(_ customerInfo: CustomerInfo,
timestamp: Date) async

}

@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
final class DiagnosticsTracker: DiagnosticsTrackerType {

private let diagnosticsFileHandler: DiagnosticsFileHandler
private let diagnosticsFileHandler: DiagnosticsFileHandlerType

init(diagnosticsFileHandler: DiagnosticsFileHandler) {
init(diagnosticsFileHandler: DiagnosticsFileHandlerType) {
self.diagnosticsFileHandler = diagnosticsFileHandler
}

func track(_ event: DiagnosticsEvent) async {
await diagnosticsFileHandler.appendEvent(diagnosticsEvent: event)
}

func trackCustomerInfoVerificationResultIfNeeded(
_ customerInfo: CustomerInfo,
timestamp: Date = Date()
) async {
let verificationResult = customerInfo.entitlements.verification
if verificationResult == .notRequested {
return
}
jamesrb1 marked this conversation as resolved.
Show resolved Hide resolved

let event = DiagnosticsEvent(
eventType: .customerInfoVerificationResult,
properties: [.verificationResultKey: AnyEncodable(verificationResult.name)],
timestamp: timestamp
)
await track(event)
}

}
13 changes: 12 additions & 1 deletion Sources/Diagnostics/Networking/DiagnosticsEventsRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ extension DiagnosticsEventsRequest.Event {
self.init(
version: event.version,
name: event.eventType.name,
properties: event.properties,
properties: event.properties.mapKeys { $0.name },
timestamp: event.timestamp.ISO8601Format()
)
}
Expand All @@ -69,6 +69,17 @@ private extension DiagnosticsEvent.EventType {

}

private extension DiagnosticsEvent.DiagnosticsPropertyKey {

var name: String {
switch self {
case .verificationResultKey:
return "verification_result"
}
}

}

// MARK: - Codable

extension DiagnosticsEventsRequest.Event: Encodable {
Expand Down
30 changes: 30 additions & 0 deletions Sources/Identity/CustomerInfoManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class CustomerInfoManager {
private let transactionFetcher: StoreKit2TransactionFetcherType
private let transactionPoster: TransactionPosterType

private var diagnosticsTracker: DiagnosticsTrackerType?

/// Underlying synchronized data.
private let data: Atomic<Data>

Expand All @@ -47,6 +49,26 @@ class CustomerInfoManager {
self.data = .init(.init(deviceCache: deviceCache))
}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
convenience init(offlineEntitlementsManager: OfflineEntitlementsManager,
operationDispatcher: OperationDispatcher,
deviceCache: DeviceCache,
backend: Backend,
transactionFetcher: StoreKit2TransactionFetcherType,
transactionPoster: TransactionPosterType,
systemInfo: SystemInfo,
diagnosticsTracker: DiagnosticsTrackerType?
) {
self.init(offlineEntitlementsManager: offlineEntitlementsManager,
operationDispatcher: operationDispatcher,
deviceCache: deviceCache,
backend: backend,
transactionFetcher: transactionFetcher,
transactionPoster: transactionPoster,
systemInfo: systemInfo)
self.diagnosticsTracker = diagnosticsTracker
}

func fetchAndCacheCustomerInfo(appUserID: String,
isAppBackgrounded: Bool,
completion: CustomerInfoCompletion?) {
Expand Down Expand Up @@ -261,6 +283,14 @@ class CustomerInfoManager {
return self.modifyData {
let lastSentCustomerInfo = $0.lastSentCustomerInfo

if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) {
if let tracker = self.diagnosticsTracker, lastSentCustomerInfo != customerInfo {
Task {
await tracker.trackCustomerInfoVerificationResultIfNeeded(customerInfo, timestamp: Date())
}
}
}

guard !$0.customerInfoObserversByIdentifier.isEmpty, lastSentCustomerInfo != customerInfo else {
return
}
Expand Down
35 changes: 32 additions & 3 deletions Sources/Purchasing/Purchases/Purchases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void

private let syncAttributesAndOfferingsIfNeededRateLimiter = RateLimiter(maxCalls: 5, period: 60)

// swiftlint:disable:next function_body_length
// swiftlint:disable:next function_body_length cyclomatic_complexity
convenience init(apiKey: String,
appUserID: String?,
userDefaults: UserDefaults? = nil,
Expand Down Expand Up @@ -346,13 +346,42 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
operationDispatcher: operationDispatcher,
api: backend.offlineEntitlements,
systemInfo: systemInfo)
let customerInfoManager = CustomerInfoManager(offlineEntitlementsManager: offlineEntitlementsManager,

let diagnosticsFileHandler: DiagnosticsFileHandlerType? = {
guard diagnosticsEnabled, #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) else { return nil }
return DiagnosticsFileHandler()
}()

let diagnosticsTracker: DiagnosticsTrackerType? = {
if let handler = diagnosticsFileHandler, #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) {
return DiagnosticsTracker(diagnosticsFileHandler: handler)
} else {
if diagnosticsEnabled {
Logger.error(Strings.diagnostics.could_not_create_diagnostics_tracker)
}
}
return nil
}()

let customerInfoManager: CustomerInfoManager
if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) {
customerInfoManager = CustomerInfoManager(offlineEntitlementsManager: offlineEntitlementsManager,
operationDispatcher: operationDispatcher,
deviceCache: deviceCache,
backend: backend,
transactionFetcher: transactionFetcher,
transactionPoster: transactionPoster,
systemInfo: systemInfo,
diagnosticsTracker: diagnosticsTracker)
} else {
customerInfoManager = CustomerInfoManager(offlineEntitlementsManager: offlineEntitlementsManager,
operationDispatcher: operationDispatcher,
deviceCache: deviceCache,
backend: backend,
transactionFetcher: transactionFetcher,
transactionPoster: transactionPoster,
systemInfo: systemInfo)
}

let attributionDataMigrator = AttributionDataMigrator()
let subscriberAttributesManager = SubscriberAttributesManager(backend: backend,
Expand Down Expand Up @@ -424,7 +453,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) {
var diagnosticsSynchronizer: DiagnosticsSynchronizer?
if diagnosticsEnabled {
if let diagnosticsFileHandler = DiagnosticsFileHandler() {
if let diagnosticsFileHandler = diagnosticsFileHandler {
diagnosticsSynchronizer = DiagnosticsSynchronizer(internalAPI: backend.internalAPI,
handler: diagnosticsFileHandler)
} else {
Expand Down
80 changes: 32 additions & 48 deletions Tests/UnitTests/Diagnostics/DiagnosticsFileHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ import XCTest
class DiagnosticsFileHandlerTests: TestCase {

fileprivate var fileHandler: FileHandler!
fileprivate var handler: DiagnosticsFileHandler!
fileprivate var handler: DiagnosticsFileHandlerType!

override func setUp() async throws {
try await super.setUp()

try AvailabilityChecks.iOS15APIAvailableOrSkipTest()

self.fileHandler = try Self.createWithTemporaryFile()
self.handler = .init(self.fileHandler)
self.handler = DiagnosticsFileHandler(self.fileHandler)
}

override func tearDown() async throws {
Expand All @@ -40,8 +40,8 @@ class DiagnosticsFileHandlerTests: TestCase {
// MARK: - append

func testAppendEventWithProperties() async throws {
let content = DiagnosticsEvent(eventType: .httpRequestPerformed,
properties: ["key": AnyEncodable("value")],
let content = DiagnosticsEvent(eventType: .customerInfoVerificationResult,
properties: [.verificationResultKey: AnyEncodable("FAILED")],
timestamp: Date())

var entries = await self.handler.getEntries()
Expand Down Expand Up @@ -78,32 +78,15 @@ class DiagnosticsFileHandlerTests: TestCase {
// MARK: - getEntries

func testGetEntries() async throws {
let line1 = """
{
"properties": {"key": "value"},
"timestamp": "2024-04-04T12:55:59Z",
"event_type": "httpRequestPerformed",
"version": 1
}
""".trimmingWhitespacesAndNewLines
let line2 = """
{
"properties": {"key": "value"},
"timestamp": "2024-04-04T13:55:59Z",
"event_type": "httpRequestPerformed",
"version": 1
}
""".trimmingWhitespacesAndNewLines

await self.fileHandler.append(line: line1)
await self.fileHandler.append(line: line2)

let content1 = DiagnosticsEvent(eventType: .httpRequestPerformed,
properties: ["key": AnyEncodable("value")],
await self.fileHandler.append(line: Self.line1)
await self.fileHandler.append(line: Self.line2)

let content1 = DiagnosticsEvent(eventType: .customerInfoVerificationResult,
properties: [.verificationResultKey: AnyEncodable("FAILED")],
timestamp: Date(millisecondsSince1970: 1712235359000))

let content2 = DiagnosticsEvent(eventType: .httpRequestPerformed,
properties: ["key": AnyEncodable("value")],
let content2 = DiagnosticsEvent(eventType: .customerInfoVerificationResult,
properties: [.verificationResultKey: AnyEncodable("FAILED")],
timestamp: Date(millisecondsSince1970: 1712238959000))

let entries = await self.handler.getEntries()
Expand All @@ -114,25 +97,8 @@ class DiagnosticsFileHandlerTests: TestCase {
// MARK: - emptyFile

func testEmptyFile() async throws {
let line1 = """
{
"properties": {"key": "value"},
"timestamp": "2024-04-04T12:55:59Z",
"event_type": "httpRequestPerformed",
"version": 1
}
""".trimmingWhitespacesAndNewLines
let line2 = """
{
"properties": {"key": "value"},
"timestamp": "2024-04-04T13:55:59Z",
"event_type": "httpRequestPerformed",
"version": 1
}
""".trimmingWhitespacesAndNewLines

await self.fileHandler.append(line: line1)
await self.fileHandler.append(line: line2)
await self.fileHandler.append(line: Self.line1)
await self.fileHandler.append(line: Self.line2)

var data = try await self.fileHandler.readFile()
expect(data).toNot(beEmpty())
Expand All @@ -150,6 +116,24 @@ class DiagnosticsFileHandlerTests: TestCase {
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
private extension DiagnosticsFileHandlerTests {

static let line1 = """
{
"properties": ["verificationResultKey", "FAILED"],
"timestamp": "2024-04-04T12:55:59Z",
"event_type": "customerInfoVerificationResult",
"version": 1
}
""".trimmingWhitespacesAndNewLines

static let line2 = """
{
"properties": ["verificationResultKey", "FAILED"],
"timestamp": "2024-04-04T13:55:59Z",
"event_type": "customerInfoVerificationResult",
"version": 1
}
""".trimmingWhitespacesAndNewLines

static func temporaryFileURL() -> URL {
return FileManager.default
.temporaryDirectory
Expand All @@ -164,7 +148,7 @@ private extension DiagnosticsFileHandlerTests {

static func sampleEvent() -> DiagnosticsEvent {
return DiagnosticsEvent(eventType: .httpRequestPerformed,
properties: ["key": AnyEncodable("value")],
properties: [.verificationResultKey: AnyEncodable("FAILED")],
timestamp: Date())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private extension DiagnosticsSynchronizerTests {

func storeEvent(timestamp: Date = eventTimestamp1) async -> DiagnosticsEvent {
let event = DiagnosticsEvent(eventType: .httpRequestPerformed,
properties: ["key": AnyEncodable("property")],
properties: [.verificationResultKey: AnyEncodable("FAILED")],
timestamp: timestamp)
await self.handler.appendEvent(diagnosticsEvent: event)

Expand Down
Loading