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

Pp 649 i os integrate the gini endpoint for amplitude user tracking #758

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f418a23
feat(GiniCaptureSDK): remove AmplitudeBaseEvent, AmplitudeEventOption…
mrkulik Dec 12, 2024
42bf33d
feat(GiniCaptureSDK): remove JSONCodingKeys, KeyedEncodingContainer, …
mrkulik Dec 12, 2024
b228c25
feat(GiniBankAPILibrary): Add Utilities, AmplitudeBaseEvent, Amplitud…
mrkulik Dec 12, 2024
cb3a430
feat(GiniBankAPILibrary): AnalyticsService, AnalyticsServiceProtocol
mrkulik Dec 12, 2024
2e2b809
feat(GiniBankAPILibrary): Add analyticsEvent to APIMethod
mrkulik Dec 12, 2024
c0750e8
feat(GiniBankAPILibrary): Add analyticsEvent to path and headers in A…
mrkulik Dec 12, 2024
6033e7c
feat(GiniBankAPILibrary): init AnalyticsService in BankAPI
mrkulik Dec 12, 2024
5c9f672
feat(GiniCaptureSDK): integrate AnalyticsService in GiniAnalyticsMana…
mrkulik Dec 12, 2024
558ca95
feat(GiniBankSDK): inject AnalyticsService in networking coordinator
mrkulik Dec 12, 2024
63bf61d
feat(GiniCaptureSDK): remove apiKey from GiniAnalyticsConfiguration, …
mrkulik Dec 13, 2024
d04e960
feat(GiniBankSDK): remove apiKey from GiniBankNetworkingScreenApiCoor…
mrkulik Dec 13, 2024
dcd7db5
feat(GiniBankAPILibrary): remove apiKey from ClientConfiguration, Amp…
mrkulik Dec 13, 2024
e84367d
feat(GiniCaptureSDK): remove apiURL for AmplitudeService
mrkulik Dec 13, 2024
d634873
feat(GiniCaptureSDK): AmplitudeService -> GiniAnalyticsService
mrkulik Dec 13, 2024
13de7d5
feat(GiniCaptureSDK): AmplitudeService -> GiniAnalyticsService in header
mrkulik Dec 13, 2024
8939900
doc(GiniCaptureSDK): update documentation for GiniAnalyticsService
mrkulik Dec 13, 2024
b5a3674
feat(GiniCaptureSDK): update events queue name
mrkulik Dec 13, 2024
0d58a76
feat(GiniBankAPILibrary): refactor APIResource
mrkulik Dec 13, 2024
6200175
fix(GiniBankAPILibrary): Fix indentation
ValentinaIancu-Gini Dec 17, 2024
55f1701
fix(GiniBankAPILibrary): Improve code documentation for public classe…
ValentinaIancu-Gini Dec 17, 2024
07c3f2d
fix(GiniBankAPILibrary): Improve naming
ValentinaIancu-Gini Dec 17, 2024
f495c4b
fix(GiniBankAPILibrary): Fix indentation
ValentinaIancu-Gini Dec 17, 2024
fd99006
fix(GiniCaptureSDK): Improve naming and documentation
ValentinaIancu-Gini Dec 17, 2024
a8685b1
fix(GiniBankSDK): Fix indentation
ValentinaIancu-Gini Dec 17, 2024
c16c35f
refactor(GiniBankAPILibrary): Refactor encoding logic to reduce dupli…
ValentinaIancu-Gini Dec 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ enum APIMethod: ResourceMethod {
case payment(id: String)
case logErrorEvent
case configurations
case analyticsEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,33 +123,44 @@ struct APIResource<T: Decodable>: Resource {
return "/events/error"
case .configurations:
return "/configurations"
case .analyticsEvent:
return "/events/batch"
}
}

var defaultHeaders: HTTPHeaders {
let acceptHeaderKey = "Accept"
let contentTypeHeaderKey = "Content-Type"

let jsonAcceptValue = ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value
let jsonContentTypeValue = ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value
let amplitudeEventsValue = "application/vnd.gini.v1.events.amplitude"

switch method {
case .createDocument(_, _, let mimeSubType, let documentType):
return ["Accept": ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value,
"Content-Type": ContentType.content(version: apiVersion,
subtype: documentType?.name,
mimeSubtype: mimeSubType).value
]
let dynamicContentType = ContentType.content(version: apiVersion,
subtype: documentType?.name,
mimeSubtype: mimeSubType).value
return [acceptHeaderKey: jsonAcceptValue,
contentTypeHeaderKey: dynamicContentType]

case .page, .pagePreview, .documentPage:
return [:]

case .paymentRequests:
return ["Accept": ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value]
return [acceptHeaderKey: jsonAcceptValue]

case .analyticsEvent:
return [acceptHeaderKey: ContentType.json.value,
contentTypeHeaderKey: amplitudeEventsValue]

default:
return ["Accept": ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value,
"Content-Type": ContentType.content(version: apiVersion,
subtype: nil,
mimeSubtype: "json").value
]
return [acceptHeaderKey: jsonAcceptValue,
contentTypeHeaderKey: jsonContentTypeValue]
}
}

Expand All @@ -159,10 +170,12 @@ struct APIResource<T: Decodable>: Resource {
additionalHeaders: HTTPHeaders = [:],
body: Data? = nil) {
self.method = method
self.domain = apiDomain
self.params = RequestParameters(method: httpMethod,
domain = apiDomain
params = RequestParameters(method: httpMethod,
body: body)
self.params.headers = defaultHeaders.merging(additionalHeaders) { (current, _ ) in current }
params.headers = defaultHeaders.merging(additionalHeaders) {
(current, _ ) in current
}
}

func parsed(response: HTTPURLResponse, data: Data) throws -> ResponseType {
Expand All @@ -178,7 +191,9 @@ struct APIResource<T: Decodable>: Resource {
if let string = string as? ResponseType {
return string
} else {
throw GiniError.parseError(message: "Invalid string response", response: response, data: data)
throw GiniError.parseError(message: "Invalid string response",
response: response,
data: data)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import Foundation

/// The `AmplitudeBaseEvent` struct represents an event with various properties and implements encoding for serialization.
struct AmplitudeBaseEvent: Encodable, Equatable {
public struct AmplitudeBaseEvent: Encodable, Equatable {
var eventType: String
var eventProperties: [String: Any]?
var userProperties: [String: Any]?
var eventOptions: AmplitudeEventOptions

enum CodingKeys: String, CodingKey {
public enum CodingKeys: String, CodingKey {
case eventType = "event_type"
case eventProperties = "event_properties"
case userProperties = "user_properties"
Expand All @@ -36,10 +36,10 @@ struct AmplitudeBaseEvent: Encodable, Equatable {
}

/// Initializes a new instance of the `AmplitudeBaseEvent` struct.
init(eventType: String,
eventProperties: [String: Any]? = nil,
userProperties: [String: Any]? = nil,
eventOptions: AmplitudeEventOptions) {
public init(eventType: String,
eventProperties: [String: Any]? = nil,
userProperties: [String: Any]? = nil,
eventOptions: AmplitudeEventOptions) {
self.eventType = eventType
self.eventProperties = eventProperties
self.userProperties = userProperties
Expand All @@ -50,7 +50,7 @@ struct AmplitudeBaseEvent: Encodable, Equatable {
///
/// - Parameter encoder: The encoder to write data to.
/// - Throws: An error if any values are invalid for the given encoder's format.
func encode(to encoder: Encoder) throws {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(eventType, forKey: .eventType)
try container.encodeIfPresent(eventProperties, forKey: .eventProperties)
Expand All @@ -69,7 +69,7 @@ struct AmplitudeBaseEvent: Encodable, Equatable {
try container.encodeIfPresent(eventOptions.ip, forKey: .ip)
}

static func == (lhs: AmplitudeBaseEvent, rhs: AmplitudeBaseEvent) -> Bool {
public static func == (lhs: AmplitudeBaseEvent, rhs: AmplitudeBaseEvent) -> Bool {
return lhs.eventType == rhs.eventType && lhs.eventOptions.eventId == rhs.eventOptions.eventId
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

/// The `AmplitudeEventOptions` struct holds common properties for events.
struct AmplitudeEventOptions {
public struct AmplitudeEventOptions {
var userId: String?
var time: Int64?
var sessionId: Int64?
Expand All @@ -22,18 +22,18 @@ struct AmplitudeEventOptions {
var deviceBrand: String?

/// Initializes a new instance of the `AmplitudeEventOptions` struct.
init(userId: String? = nil,
deviceId: String? = nil,
time: Int64? = nil,
sessionId: Int64? = nil,
platform: String? = nil,
osVersion: String? = nil,
osName: String? = nil,
language: String? = nil,
ip: String? = nil,
eventId: Int64? = nil,
deviceModel: String? = nil,
deviceBrand: String? = nil) {
public init(userId: String? = nil,
deviceId: String? = nil,
time: Int64? = nil,
sessionId: Int64? = nil,
platform: String? = nil,
osVersion: String? = nil,
osName: String? = nil,
language: String? = nil,
ip: String? = nil,
eventId: Int64? = nil,
deviceModel: String? = nil,
deviceBrand: String? = nil) {
self.userId = userId
self.deviceId = deviceId
self.time = time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@

import Foundation
/**
A struct representing the payload for batching events to be sent to the Amplitude server.
A struct representing the payload for batching events to be sent to the server.

This struct conforms to the `Encodable` protocol to facilitate easy encoding
to JSON format. It includes the API key and an array of events to be uploaded.
to JSON format. It includes an array of events to be uploaded.

- Parameters:
- apiKey: The API key for the Amplitude analytics platform.
- events: An array of `AmplitudeBaseEvent` objects to be included in the batch upload.
*/
struct AmplitudeEventsBatchPayload: Encodable {
let apiKey: String
public struct AmplitudeEventsBatchPayload: Encodable {
let events: [AmplitudeBaseEvent]

/**
Customizes the coding keys for the `AmplitudeEventsBatchPayload` struct to match the expected JSON format.

- apiKey: Encoded as "api_key" in the JSON payload.
- events: Encoded as "events" in the JSON payload.
*/
enum CodingKeys: String, CodingKey {
case apiKey = "api_key"
case events
}

public init(events: [AmplitudeBaseEvent]) {
self.events = events
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// AnalyticsService.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//

import Foundation

/// `AnalyticsService` is responsible for sending batched analytics events to a server.
/// It utilizes a session manager to manage network requests and communicates with a configurable API domain.
public final class AnalyticsService: AnalyticsServiceProtocol {

// MARK: - Properties

/// The session manager responsible for handling network requests.
/// This must conform to the `SessionManagerProtocol`.
let sessionManager: SessionManagerProtocol

/// The API domain used for sending analytics event requests.
/// This determines the base URL for API communication.
public var apiDomain: APIDomain

// MARK: - Initialization

/**
Initializes a new instance of `AnalyticsService`.

- Parameters:
- sessionManager: An object conforming to `SessionManagerProtocol` responsible for managing network requests.
- apiDomain: The domain of the API for sending analytics events. Defaults to `.default`.

- Important: The `sessionManager` is required to perform network communication,
and the `apiDomain` configures the target API server.
*/
init(sessionManager: SessionManagerProtocol, apiDomain: APIDomain = .default) {
self.sessionManager = sessionManager
self.apiDomain = apiDomain
}

// MARK: - Public Methods

/**
Sends a batch of analytics events payload to the server.

- Parameters:
- payload: The `AmplitudeEventsBatchPayload` object containing a batch of analytics events to send.
- completion: A closure called when the request is completed.
It provides a `Result` containing either a `String` on success or a `GiniError` on failure.

- Note: Internally, this method delegates the request to `sessionManager` for execution.
*/
public func sendEventsPayload(payload: AmplitudeEventsBatchPayload,
completion: @escaping CompletionResult<String>) {
sendEventsPayload(payload: payload,
resourceHandler: sessionManager.data,
completion: completion)
}
}

extension AnalyticsService {
// MARK: - Private Helper Methods

/**
Sends the analytics events payload using the provided resource handler.

- Parameters:
- payload: The `AmplitudeEventsBatchPayload` object containing the events data.
- resourceHandler: A handler for performing the network request, provided by `sessionManager`.
- completion: A closure called when the request completes with success or failure.

- Note:
The payload is encoded using `JSONEncoder`.
*/
func sendEventsPayload(payload: AmplitudeEventsBatchPayload,
resourceHandler: ResourceDataHandler<APIResource<String>>,
completion: @escaping CompletionResult<String>) {
let resource = APIResource<String>(
method: .analyticsEvent,
apiDomain: apiDomain,
httpMethod: .post,
additionalHeaders: [:],
body: try? JSONEncoder().encode(payload)
)
sessionManager.data(resource: resource, completion: completion)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// AnalyticsServiceProtocol.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//


import Foundation

/// A protocol defining the contract for an analytics service responsible for sending event payloads.
public protocol AnalyticsServiceProtocol: AnyObject {

/**
Sends a batch of analytics events payload to the server.

- Parameters:
- payload: An `AmplitudeEventsBatchPayload` object containing the events to be sent.
- completion: A closure to be called with the result of the request.
It provides a `Result<String, GiniError>` indicating success or failure.

- Note: Conforming types are expected to provide their own implementation for this method.
*/
func sendEventsPayload(payload: AmplitudeEventsBatchPayload,
completion: @escaping CompletionResult<String>)
}

extension AnalyticsServiceProtocol {

/**
A default implementation of `sendEventsPayload` for types conforming to `AnalyticsServiceProtocol`.

- Parameters:
- payload: An `AmplitudeEventsBatchPayload` object containing the events to be sent.
- resourceHandler: A handler for performing network requests, usually provided by a session manager.
- completion: A closure to be called with the result of the network operation.

- Note:
This default implementation is intentionally left **empty**. Conforming types
can override this method to provide a specific implementation if needed.
*/
func sendEventsPayload(payload: AmplitudeEventsBatchPayload,
resourceHandler: ResourceDataHandler<APIResource<String>>,
completion: @escaping CompletionResult<String>) {
// Default implementation is empty
}
}
Loading