Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d272d2c
Change FPTIBatchData structure
richherrera Feb 26, 2025
304190d
Add NetworkClient and AnalyticsServices files
richherrera Feb 26, 2025
03f9365
Add Sessionable and NetworkError files
richherrera Feb 27, 2025
52fd81c
Add Network errors
richherrera Feb 27, 2025
6224055
Add Sessionable Protocol and Extension
richherrera Feb 27, 2025
4a4e97f
Sort files
richherrera Feb 27, 2025
4f87b20
Add NetworkClient
richherrera Feb 27, 2025
0d71ffb
Implement AnalyticsService class
richherrera Feb 27, 2025
060e7e4
Add Private Mark
richherrera Feb 27, 2025
ddb944c
Add necessary files (TestPlan, mock files and UT files)
richherrera Feb 27, 2025
37ea28a
Setup Test Plan
richherrera Feb 27, 2025
05cf278
Create MockSession class and NonEncodable structure
richherrera Feb 27, 2025
6310175
Add NetworkClient tests
richherrera Feb 27, 2025
b03bdac
Create MockNetworkClient class
richherrera Feb 27, 2025
d6fc4fc
Sort files
richherrera Feb 27, 2025
022530d
Add missing blank space
richherrera Feb 27, 2025
c9e846c
Add UTs to test success and failures
richherrera Feb 27, 2025
292affb
Remove iOS 15 validation
richherrera Feb 27, 2025
5308717
Add analytics calls
richherrera Feb 28, 2025
a268cd0
Set sessionID value
richherrera Mar 3, 2025
9734fb3
Add AnalyticsService property
richherrera Mar 3, 2025
3ba4d21
Add MockAnalyticsService
richherrera Mar 3, 2025
700f695
Fix PopupBridge tests adding mock
richherrera Mar 3, 2025
fd33535
Increase time interval
richherrera Mar 3, 2025
d0a8d8d
Add MockAnalyticsService
richherrera Mar 4, 2025
147dc9b
Add PopupBrdige UTs
richherrera Mar 4, 2025
9a5e980
Strongly type FPTIBatchData
richherrera Mar 5, 2025
a658633
Add catch block
richherrera Mar 6, 2025
70bb98e
Update Sources/PopupBridge/FPTIBatchData.swift
richherrera Mar 10, 2025
c8fd4cf
Add analytics folder
richherrera Mar 14, 2025
5911546
Remove test plan
richherrera Mar 14, 2025
7bc9147
Address feedback
richherrera Mar 18, 2025
39a61a6
Merge branch 'v3' into add-networking
richherrera Mar 18, 2025
ec82e7c
Sort files
richherrera Mar 18, 2025
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
2 changes: 1 addition & 1 deletion Demo/UITests/PopupBridge_DemoUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ final class PopupBridge_DemoUITests: XCTestCase {

// MARK: - Helpers

func waitForElement(_ element: XCUIElement, timeout: TimeInterval = 10) {
func waitForElement(_ element: XCUIElement, timeout: TimeInterval = 15) {
expectation(for: NSPredicate(format: "exists ==1"), evaluatedWith: element)
waitForExpectations(timeout: timeout)
}
Expand Down
40 changes: 40 additions & 0 deletions PopupBridge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@
objects = {

/* Begin PBXBuildFile section */
454C7EC32D6E351F00CA3191 /* NetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454C7EC22D6E351900CA3191 /* NetworkClient.swift */; };
45AE41D22D7649F800388548 /* MockAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE41D12D7649EE00388548 /* MockAnalyticsService.swift */; };
45CD0C2C2D64F08F0072C5A4 /* FPTIBatchData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD0C2B2D64F0810072C5A4 /* FPTIBatchData.swift */; };
45CD0C2E2D664D140072C5A4 /* Date+MilisecondTimestamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD0C2D2D664CF50072C5A4 /* Date+MilisecondTimestamp.swift */; };
45CD0C302D6788A10072C5A4 /* Bundle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD0C2F2D67888F0072C5A4 /* Bundle+Extension.swift */; };
45CD0C322D6793FB0072C5A4 /* PopupBridgeAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD0C312D6793F90072C5A4 /* PopupBridgeAnalytics.swift */; };
45FBAF272D6F9CCF000D550B /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBAF262D6F9CC6000D550B /* AnalyticsService.swift */; };
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add all of the new files that were specifically added for analytics to an Analytics folder under Sources/PopupBridge? nitpick, but would help navigation of the PopupBridge source files

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated: c8fd4cf

45FBAF292D701AFE000D550B /* Sessionable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBAF282D701AF7000D550B /* Sessionable.swift */; };
45FBAF2B2D701B12000D550B /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBAF2A2D701B0D000D550B /* NetworkError.swift */; };
45FBAF2D2D701DD2000D550B /* NetworkClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBAF2C2D701DD2000D550B /* NetworkClient_Tests.swift */; };
45FBAF2F2D701E24000D550B /* MockSessionable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBAF2E2D701E1D000D550B /* MockSessionable.swift */; };
45FBAF312D70C27A000D550B /* TestPlan.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 45FBAF302D70C27A000D550B /* TestPlan.xctestplan */; };
45FBAF332D70CE9E000D550B /* MockNetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBAF322D70CE94000D550B /* MockNetworkClient.swift */; };
45FBAF352D70CFB6000D550B /* AnalyticsService_Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBAF342D70CFB6000D550B /* AnalyticsService_Test.swift */; };
62D5EC522B9F753100D09C5D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 62D5EC512B9F753100D09C5D /* PrivacyInfo.xcprivacy */; };
79DB9F7F53319F206CDE119E /* Pods_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28245E4F1AC5126D54985D88 /* Pods_UnitTests.framework */; };
800A09D82995F143003ED16E /* POPPopupBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 800A09D72995F143003ED16E /* POPPopupBridge.swift */; };
Expand All @@ -30,10 +40,20 @@

/* Begin PBXFileReference section */
28245E4F1AC5126D54985D88 /* Pods_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
454C7EC22D6E351900CA3191 /* NetworkClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkClient.swift; sourceTree = "<group>"; };
45AE41D12D7649EE00388548 /* MockAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalyticsService.swift; sourceTree = "<group>"; };
45CD0C2B2D64F0810072C5A4 /* FPTIBatchData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPTIBatchData.swift; sourceTree = "<group>"; };
45CD0C2D2D664CF50072C5A4 /* Date+MilisecondTimestamp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+MilisecondTimestamp.swift"; sourceTree = "<group>"; };
45CD0C2F2D67888F0072C5A4 /* Bundle+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extension.swift"; sourceTree = "<group>"; };
45CD0C312D6793F90072C5A4 /* PopupBridgeAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupBridgeAnalytics.swift; sourceTree = "<group>"; };
45FBAF262D6F9CC6000D550B /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
45FBAF282D701AF7000D550B /* Sessionable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sessionable.swift; sourceTree = "<group>"; };
45FBAF2A2D701B0D000D550B /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = "<group>"; };
45FBAF2C2D701DD2000D550B /* NetworkClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkClient_Tests.swift; sourceTree = "<group>"; };
45FBAF2E2D701E1D000D550B /* MockSessionable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSessionable.swift; sourceTree = "<group>"; };
45FBAF302D70C27A000D550B /* TestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestPlan.xctestplan; sourceTree = "<group>"; };
45FBAF322D70CE94000D550B /* MockNetworkClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNetworkClient.swift; sourceTree = "<group>"; };
45FBAF342D70CFB6000D550B /* AnalyticsService_Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService_Test.swift; sourceTree = "<group>"; };
4EF7C7DDAB0B99FF28DD6541 /* Pods-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.debug.xcconfig"; path = "Target Support Files/Pods-UnitTests/Pods-UnitTests.debug.xcconfig"; sourceTree = "<group>"; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -144,17 +164,23 @@
isa = PBXGroup;
children = (
A775A08C1DEE4EF0009E67C2 /* PopupBridge */,
45FBAF302D70C27A000D550B /* TestPlan.xctestplan */,
);
path = Sources;
sourceTree = "<group>";
};
A775A07D1DEE4E7E009E67C2 /* UnitTests */ = {
isa = PBXGroup;
children = (
45FBAF342D70CFB6000D550B /* AnalyticsService_Test.swift */,
A775A0801DEE4E7E009E67C2 /* Info.plist */,
45AE41D12D7649EE00388548 /* MockAnalyticsService.swift */,
45FBAF322D70CE94000D550B /* MockNetworkClient.swift */,
BE2524552A17FB9F00168D77 /* MockScriptMessage.swift */,
45FBAF2E2D701E1D000D550B /* MockSessionable.swift */,
BE2524532A17DFCC00168D77 /* MockUserContentController.swift */,
BEF9ED202A2A4896005D54AB /* MockWebAuthenticationSession.swift */,
45FBAF2C2D701DD2000D550B /* NetworkClient_Tests.swift */,
BE2524512A17DF8200168D77 /* PopupBridge_UnitTests.swift */,
);
path = UnitTests;
Expand All @@ -163,15 +189,19 @@
A775A08C1DEE4EF0009E67C2 /* PopupBridge */ = {
isa = PBXGroup;
children = (
45FBAF262D6F9CC6000D550B /* AnalyticsService.swift */,
45CD0C2F2D67888F0072C5A4 /* Bundle+Extension.swift */,
45CD0C2D2D664CF50072C5A4 /* Date+MilisecondTimestamp.swift */,
45CD0C2B2D64F0810072C5A4 /* FPTIBatchData.swift */,
454C7EC22D6E351900CA3191 /* NetworkClient.swift */,
45FBAF2A2D701B0D000D550B /* NetworkError.swift */,
800A09D72995F143003ED16E /* POPPopupBridge.swift */,
A79330F01DF0F98F00EE479D /* PopupBridge-Framework-Info.plist */,
45CD0C312D6793F90072C5A4 /* PopupBridgeAnalytics.swift */,
BEF9ED222A2A6A2C005D54AB /* PopupBridgeConstants.swift */,
8079D7192996F6C200A2E336 /* PopupBridgeUserScript.swift */,
62D5EC512B9F753100D09C5D /* PrivacyInfo.xcprivacy */,
45FBAF282D701AF7000D550B /* Sessionable.swift */,
800E789E29E09A2A00D1B0FC /* URLDetailsPayload.swift */,
BE8E37B52A17B79E00181FDA /* WebAuthenticationSession.swift */,
800E789C29E0958A00D1B0FC /* WebViewMessage.swift */,
Expand Down Expand Up @@ -281,6 +311,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
45FBAF312D70C27A000D550B /* TestPlan.xctestplan in Resources */,
62D5EC522B9F753100D09C5D /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -317,20 +348,29 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
45FBAF2F2D701E24000D550B /* MockSessionable.swift in Sources */,
BE2524542A17DFCC00168D77 /* MockUserContentController.swift in Sources */,
45AE41D22D7649F800388548 /* MockAnalyticsService.swift in Sources */,
BE2524562A17FB9F00168D77 /* MockScriptMessage.swift in Sources */,
BEF9ED212A2A4896005D54AB /* MockWebAuthenticationSession.swift in Sources */,
BE2524522A17DF8200168D77 /* PopupBridge_UnitTests.swift in Sources */,
45FBAF332D70CE9E000D550B /* MockNetworkClient.swift in Sources */,
45FBAF352D70CFB6000D550B /* AnalyticsService_Test.swift in Sources */,
45FBAF2D2D701DD2000D550B /* NetworkClient_Tests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
A775A0861DEE4EF0009E67C2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
45FBAF292D701AFE000D550B /* Sessionable.swift in Sources */,
45FBAF2B2D701B12000D550B /* NetworkError.swift in Sources */,
800E789F29E09A2A00D1B0FC /* URLDetailsPayload.swift in Sources */,
45CD0C302D6788A10072C5A4 /* Bundle+Extension.swift in Sources */,
45FBAF272D6F9CCF000D550B /* AnalyticsService.swift in Sources */,
BEF9ED232A2A6A2C005D54AB /* PopupBridgeConstants.swift in Sources */,
454C7EC32D6E351F00CA3191 /* NetworkClient.swift in Sources */,
800A09D82995F143003ED16E /* POPPopupBridge.swift in Sources */,
BE8E37B62A17B79E00181FDA /* WebAuthenticationSession.swift in Sources */,
45CD0C2C2D64F08F0072C5A4 /* FPTIBatchData.swift in Sources */,
Expand Down
42 changes: 42 additions & 0 deletions Sources/PopupBridge/AnalyticsService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation

protocol AnalyticsServiceable {
func sendAnalyticsEvent(_ eventName: String, sessionID: String)
}

final class AnalyticsService: AnalyticsServiceable {

// MARK: - Private Properties

/// The FPTI URL to post all analytic events.
private let url = URL(string: "https://api.paypal.com/v1/tracking/batch/events")!
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we aren't actually batching, FPTI has a separate endpoint for single event upload /v1/tracking/events/. The format is slightly different from /v1/tracking/batch/events/. I'm not sure if there are performance benefits to using the single event API vs the multiple event API, but we could confirm in #help-fpti.

To see the structure of each, you can look at the API details in PPAAS (I also have it in a Postman collection)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's the endpoint used in PPCP, however, we would need a custom encode method. I did a POC with those changes and was thinking that it would be better to have the same implementation we have in BT, which is why I opted for this implementation. While working on BT v7, we chose not to use a custom encode method. Do you think we could use it for this repo?

private let networkClient: Networkable

// MARK: - Initializer

init(networkClient: Networkable = NetworkClient()) {
self.networkClient = networkClient
}

// MARK: - Internal Methods

func sendAnalyticsEvent(_ eventName: String, sessionID: String) {
Task(priority: .background) {
await performEventRequest(eventName, sessionID: sessionID)
}
}

func performEventRequest(_ eventName: String, sessionID: String) async {
let body = createAnalyticsEvent(eventName: eventName, sessionID: sessionID)
try? await networkClient.post(url: url, body: body)
}

// MARK: - Private Methods

/// Constructs POST params to be sent to FPTI
private func createAnalyticsEvent(eventName: String, sessionID: String) -> Codable {
let batchMetadata = FPTIBatchData.Metadata(sessionID: sessionID)
let event = FPTIBatchData.Event(eventName: eventName)
return FPTIBatchData(metadata: batchMetadata, events: [event])
}
}
169 changes: 101 additions & 68 deletions Sources/PopupBridge/FPTIBatchData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,110 @@ import UIKit

struct FPTIBatchData: Codable {

let appID: String = Bundle.main.infoDictionary?[kCFBundleIdentifierKey as String] as? String ?? "N/A"

let appName: String = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? "N/A"

let clientSDKVersion: String = Bundle.clientSDKVersion

let clientOS: String = UIDevice.current.systemName + " " + UIDevice.current.systemVersion

let component: String = "popupbridgesdk"

let deviceManufacturer: String = "Apple"
let events: [EventsContainer]

init(metadata: Metadata, events fptiEvents: [Event]) {
self.events = [
EventsContainer(
metadata: metadata,
fptiEvents: fptiEvents
)
]
}

let deviceModel: String = {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
struct EventsContainer: Codable {

let metadata: Metadata
let fptiEvents: [Event]

enum CodingKeys: String, CodingKey {
case metadata = "batch_params"
case fptiEvents = "event_params"
}
return identifier
}()

let eventName: String

let eventSource: String = "mobile-native"

let isSimulator: Bool = {
#if targetEnvironment(simulator)
true
#else
false
#endif
}()

let merchantAppVersion: String = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String ?? "N/A"

let packageManager: String = {
#if COCOAPODS
"CocoaPods"
#elseif SWIFT_PACKAGE
"Swift Package Manager"
#else
"Carthage or Other"
#endif
}()

let platform: String = "iOS"

let sessionID: String

let timestamp: String = String(Date().utcTimestampMilliseconds)
}

let tenantName: String = "Braintree"
/// Encapsulates a single event by it's name and timestamp.
struct Event: Codable {

let eventName: String

let timestamp: String = String(Date().utcTimestampMilliseconds)

let tenantName: String = "Braintree"

enum CodingKeys: String, CodingKey {
case eventName = "event_name"
case timestamp = "t"
case tenantName = "tenant_name"
}
}

enum CodingKeys: String, CodingKey {
case appID = "app_id"
case appName = "app_name"
case clientSDKVersion = "c_sdk_ver"
case clientOS = "client_os"
case component = "comp"
case deviceManufacturer = "device_manufacturer"
case deviceModel = "mobile_device_model"
case eventName = "event_name"
case eventSource = "event_source"
case isSimulator = "is_simulator"
case merchantAppVersion = "mapv"
case packageManager = "ios_package_manager"
case platform
case sessionID = "session_id"
case timestamp = "t"
case tenantName = "tenant_name"
/// The FPTI tags/ metadata applicable to all events in the batch upload.
struct Metadata: Codable {

let appID: String = Bundle.main.infoDictionary?[kCFBundleIdentifierKey as String] as? String ?? "N/A"

let appName: String = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? "N/A"

let clientSDKVersion: String = Bundle.clientSDKVersion

let clientOS: String = UIDevice.current.systemName + " " + UIDevice.current.systemVersion

let component: String = "popupbridgesdk"

let deviceManufacturer: String = "Apple"

let deviceModel: String = {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}()

let eventSource: String = "mobile-native"

let isSimulator: Bool = {
#if targetEnvironment(simulator)
true
#else
false
#endif
}()

let merchantAppVersion: String = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String ?? "N/A"

let packageManager: String = {
#if COCOAPODS
"CocoaPods"
#elseif SWIFT_PACKAGE
"Swift Package Manager"
#else
"Carthage or Other"
#endif
}()

let platform: String = "iOS"

let sessionID: String

enum CodingKeys: String, CodingKey {
case appID = "app_id"
case appName = "app_name"
case clientSDKVersion = "c_sdk_ver"
case clientOS = "client_os"
case component = "comp"
case deviceManufacturer = "device_manufacturer"
case deviceModel = "mobile_device_model"
case eventSource = "event_source"
case isSimulator = "is_simulator"
case merchantAppVersion = "mapv"
case packageManager = "ios_package_manager"
case platform
case sessionID = "session_id"
}
}
}
40 changes: 40 additions & 0 deletions Sources/PopupBridge/NetworkClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation

protocol Networkable {
func post<T: Encodable>(url: URL, body: T) async throws
}

final class NetworkClient: Networkable {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we know that PopupBridge doesn't do any other networking besides just sending a single POST for analytics, do we think it's worth having this as a separate layer from AnalyticsService? Thinking of the KISS principe

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated: 7bc9147


// MARK: - Private Properties

private let session: Sessionable

// MARK: - Initializer

init(session: Sessionable = URLSession.shared) {
self.session = session
}

// MARK: - Internal Methods

func post<T: Encodable>(url: URL, body: T) async throws {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.allHTTPHeaderFields = ["Content-Type": "application/json"]

do {
let encodedBody = try JSONEncoder().encode(body)
request.httpBody = encodedBody
} catch let encodingError {
throw NetworkError.encodingError(encodingError)
}

let (_, response) = try await session.data(for: request)

guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
}
}
Loading