Files
purchases-ios/Sources/Events/FeatureEvents/Networking/FeatureEventsRequest+CheckpointEvent.swift
T
Facundo MenzellaandClaude Opus 5.5 06cd8c171c other: Add trace_id to the checkpoint hit event (#7811)
* Add trace_id to the checkpoint hit event

The id is created when the checkpoint is evaluated and handed to the workflow it
starts, so the hit, the workflow's step and paywall events and the receipt's
paywall trace_id all share it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Drop Android references from trace id comments

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Remove remaining Android references in workflow event comments

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 10:43:35 +02:00

124 lines
4.1 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// FeatureEventsRequest+CheckpointEvent.swift
import Foundation
/// Type alias to avoid naming conflict inside `FeatureEventsRequest.CheckpointEvent`.
private typealias StoredCheckpointEvent = CheckpointEvent
extension FeatureEventsRequest {
/// Khepri-compatible wire format for checkpoint hits.
struct CheckpointEvent {
let id: String
let version: Int
let type: String
let identifier: String
let appUserID: String
let appSessionID: String
let timestamp: UInt64
let checkpointType: String
let result: String
let workflowID: String?
let offeringID: String?
let checkpointRuleID: String?
let traceID: String?
}
}
extension FeatureEventsRequest.CheckpointEvent {
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
init?(storedEvent: StoredFeatureEvent) {
guard storedEvent.feature == .checkpoints else { return nil }
guard let appSessionID = storedEvent.appSessionID else {
Logger.error(Strings.paywalls.event_missing_app_session_id)
return nil
}
guard let jsonData = storedEvent.encodedEvent.data(using: .utf8) else {
Logger.error(Strings.paywalls.event_cannot_get_encoded_event)
return nil
}
do {
let event = try JSONDecoder.default.decode(StoredCheckpointEvent.self, from: jsonData)
self.init(
id: event.data.id.uuidString,
version: Self.schemaVersion,
type: event.eventType,
identifier: event.data.identifier,
appUserID: storedEvent.userID,
appSessionID: appSessionID.uuidString,
timestamp: event.data.date.millisecondsSince1970,
checkpointType: event.data.checkpointType.rawValue,
result: event.data.result.rawValue,
workflowID: event.data.workflowID,
offeringID: event.data.offeringID,
checkpointRuleID: event.data.checkpointRuleID,
traceID: event.data.traceID
)
} catch {
Logger.error(Strings.paywalls.event_cannot_deserialize(error))
return nil
}
}
private static let schemaVersion = 1
}
// MARK: - Encodable
extension FeatureEventsRequest.CheckpointEvent: Encodable {
private enum CodingKeys: String, CodingKey {
case id
case version
case type
case identifier
case appUserID = "app_user_id"
case appSessionID = "app_session_id"
case timestamp
case checkpointType = "checkpoint_type"
case result
case workflowID = "workflow_id"
case offeringID = "offering_id"
case checkpointRuleID = "checkpoint_rule_id"
case traceID = "trace_id"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.version, forKey: .version)
try container.encode(self.type, forKey: .type)
try container.encode(self.identifier, forKey: .identifier)
try container.encode(self.appUserID, forKey: .appUserID)
try container.encode(self.appSessionID, forKey: .appSessionID)
try container.encode(self.timestamp, forKey: .timestamp)
try container.encode(self.checkpointType, forKey: .checkpointType)
try container.encode(self.result, forKey: .result)
try container.encodeIfPresent(self.workflowID, forKey: .workflowID)
try container.encodeIfPresent(self.offeringID, forKey: .offeringID)
try container.encodeIfPresent(self.checkpointRuleID, forKey: .checkpointRuleID)
try container.encodeIfPresent(self.traceID, forKey: .traceID)
}
}