Skip to content

Commit

Permalink
Merge pull request #1380 from WalletConnect/events-sdk
Browse files Browse the repository at this point in the history
Events sdk
  • Loading branch information
llbartekll authored Jul 9, 2024
2 parents 57f6ae5 + a4e1f36 commit 5314c59
Show file tree
Hide file tree
Showing 40 changed files with 934 additions and 85 deletions.
54 changes: 54 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/EventsTests.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EventsTests"
BuildableName = "EventsTests"
BlueprintName = "EventsTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
3 changes: 2 additions & 1 deletion Example/IntegrationTests/Pairing/PairingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ final class PairingTests: XCTestCase {
logger: logger,
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
networkingClient: networkingClient)
networkingClient: networkingClient,
eventsClient: MockEventsClient())


return pairingClient
Expand Down
6 changes: 4 additions & 2 deletions Example/IntegrationTests/Sign/SignClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ final class SignClientTests: XCTestCase {
logger: logger,
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
networkingClient: networkingClient
networkingClient: networkingClient,
eventsClient: MockEventsClient()
)
let metadata = AppMetadata(name: name, description: "", url: "", icons: [""], redirect: try! AppMetadata.Redirect(native: "", universal: linkModeUniversalLink, linkMode: supportLinkMode))

Expand All @@ -61,7 +62,8 @@ final class SignClientTests: XCTestCase {
networkingClient: networkingClient,
iatProvider: IATProviderMock(),
projectId: InputConfig.projectId,
crypto: DefaultCryptoProvider()
crypto: DefaultCryptoProvider(),
eventsClient: MockEventsClient()
)

let clientId = try! networkingClient.getClientId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ final class XPlatformW3WTests: XCTestCase {
logger: pairingLogger,
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
networkingClient: networkingClient)
networkingClient: networkingClient,
eventsClient: MockEventsClient())

let signClient = SignClientFactory.create(
metadata: AppMetadata(name: name, description: "", url: "", icons: [""], redirect: try! AppMetadata.Redirect(native: "", universal: nil)),
Expand All @@ -57,7 +58,8 @@ final class XPlatformW3WTests: XCTestCase {
networkingClient: networkingClient,
iatProvider: DefaultIATProvider(),
projectId: InputConfig.projectId,
crypto: DefaultCryptoProvider()
crypto: DefaultCryptoProvider(),
eventsClient: MockEventsClient()
)

w3wClient = Web3WalletClientFactory.create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ final class ConfigurationService {

Notify.instance.setLogging(level: .debug)
Sign.instance.setLogging(level: .debug)
Events.instance.setLogging(level: .debug)

if let clientId = try? Networking.interactor.getClientId() {
LoggingService.instance.setUpUser(account: importAccount.account.absoluteString, clientId: clientId)
Expand Down
12 changes: 9 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ let package = Package(
targets: [
.target(
name: "WalletConnectSign",
dependencies: ["WalletConnectPairing", "WalletConnectVerify", "WalletConnectSigner"],
dependencies: ["WalletConnectPairing", "WalletConnectVerify", "WalletConnectSigner", "Events"],
path: "Sources/WalletConnectSign",
resources: [.process("Resources/PrivacyInfo.xcprivacy")]),
.target(
Expand Down Expand Up @@ -84,7 +84,7 @@ let package = Package(
path: "Sources/WalletConnectKMS"),
.target(
name: "WalletConnectPairing",
dependencies: ["WalletConnectNetworking"],
dependencies: ["WalletConnectNetworking", "Events"],
resources: [.process("Resources/PrivacyInfo.xcprivacy")]),
.target(
name: "WalletConnectSigner",
Expand Down Expand Up @@ -126,6 +126,9 @@ let package = Package(
.target(
name: "Database",
dependencies: ["WalletConnectUtils"]),
.target(
name: "Events",
dependencies: ["WalletConnectUtils", "WalletConnectNetworking"]),
.target(
name: "WalletConnectModal",
dependencies: ["QRCode", "WalletConnectSign"],
Expand Down Expand Up @@ -169,7 +172,10 @@ let package = Package(
dependencies: ["Commons", "TestingUtils"]),
.testTarget(
name: "WalletConnectModalTests",
dependencies: ["WalletConnectModal", "TestingUtils"])
dependencies: ["WalletConnectModal", "TestingUtils"]),
.testTarget(
name: "EventsTests",
dependencies: ["Events"]),
],
swiftLanguageVersions: [.v5]
)
19 changes: 19 additions & 0 deletions Sources/Events/Event.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

struct Event: Codable {
let eventId: String
let bundleId: String
let timestamp: Int64
let props: Props
}

struct Props: Codable {
let event: String
let type: String
let properties: Properties?
}

struct Properties: Codable {
let topic: String?
let trace: [String]?
}
57 changes: 57 additions & 0 deletions Sources/Events/EventStorage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

import Foundation

protocol EventStorage {
func saveErrorEvent(_ event: Event)
func fetchErrorEvents() -> [Event]
func clearErrorEvents()
}

class UserDefaultsEventStorage: EventStorage {
private let errorEventsKey = "com.walletconnect.sdk.errorEvents"
private let maxEvents = 30

func saveErrorEvent(_ event: Event) {
var existingEvents = fetchErrorEvents()
existingEvents.append(event)
// Ensure we keep only the last 30 events
if existingEvents.count > maxEvents {
existingEvents = Array(existingEvents.suffix(maxEvents))
}
if let encoded = try? JSONEncoder().encode(existingEvents) {
UserDefaults.standard.set(encoded, forKey: errorEventsKey)
}
}

func fetchErrorEvents() -> [Event] {
if let data = UserDefaults.standard.data(forKey: errorEventsKey),
let events = try? JSONDecoder().decode([Event].self, from: data) {
// Return only the last 30 events
return Array(events.suffix(maxEvents))
}
return []
}

func clearErrorEvents() {
UserDefaults.standard.removeObject(forKey: errorEventsKey)
}
}

#if DEBUG
class MockEventStorage: EventStorage {
private(set) var savedEvents: [Event] = []

func saveErrorEvent(_ event: Event) {
savedEvents.append(event)
}

func fetchErrorEvents() -> [Event] {
return savedEvents
}

func clearErrorEvents() {
savedEvents.removeAll()
}
}
#endif

11 changes: 11 additions & 0 deletions Sources/Events/Events.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

public class Events {
/// Singleton instance of EventsClient
public static var instance: EventsClient = {
return EventsClientFactory.create(
projectId: Networking.projectId,
sdkVersion: EnvironmentInfo.sdkName
)
}()
}
107 changes: 107 additions & 0 deletions Sources/Events/EventsClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import Foundation

public protocol EventsClientProtocol {
func startTrace(topic: String)
func saveEvent(_ event: TraceEvent)
func setTopic(_ topic: String)
func setTelemetryEnabled(_ enabled: Bool)
}

public class EventsClient: EventsClientProtocol {
private let eventsCollector: EventsCollector
private let eventsDispatcher: EventsDispatcher
private let logger: ConsoleLogging
private var stateStorage: TelemetryStateStorage

init(
eventsCollector: EventsCollector,
eventsDispatcher: EventsDispatcher,
logger: ConsoleLogging,
stateStorage: TelemetryStateStorage
) {
self.eventsCollector = eventsCollector
self.eventsDispatcher = eventsDispatcher
self.logger = logger
self.stateStorage = stateStorage

if stateStorage.telemetryEnabled {
Task { await sendStoredEvents() }
} else {
self.eventsCollector.storage.clearErrorEvents()
}
}

public func setLogging(level: LoggingLevel) {
logger.setLogging(level: level)
}

// Public method to start trace
public func startTrace(topic: String) {
guard stateStorage.telemetryEnabled else { return }
logger.debug("Will start trace with topic: \(topic)")
eventsCollector.startTrace(topic: topic)
}

public func setTopic(_ topic: String) {
guard stateStorage.telemetryEnabled else { return }
eventsCollector.setTopic(topic)
}

// Public method to save event
public func saveEvent(_ event: TraceEvent) {
guard stateStorage.telemetryEnabled else { return }
logger.debug("Will store an event: \(event)")
eventsCollector.saveEvent(event)
}

// Public method to set telemetry enabled or disabled
public func setTelemetryEnabled(_ enabled: Bool) {
stateStorage.telemetryEnabled = enabled
if enabled {
Task { await sendStoredEvents() }
} else {
eventsCollector.storage.clearErrorEvents()
}
}

private func sendStoredEvents() async {
guard stateStorage.telemetryEnabled else { return }
let events = eventsCollector.storage.fetchErrorEvents()
guard !events.isEmpty else { return }

logger.debug("Will send events")
do {
let success: Bool = try await eventsDispatcher.executeWithRetry(events: events)
if success {
logger.debug("Events sent successfully")
self.eventsCollector.storage.clearErrorEvents()
}
} catch {
logger.debug("Failed to send events after multiple attempts: \(error)")
}
}
}

#if DEBUG
public class MockEventsClient: EventsClientProtocol {
var startTraceCalled = false
var saveEventCalled = false
var telemetryEnabled = true

public init() {}

public func startTrace(topic: String) {
startTraceCalled = true
}

public func setTopic(_ topic: String) {}

public func saveEvent(_ event: TraceEvent) {
saveEventCalled = true
}

public func setTelemetryEnabled(_ enabled: Bool) {
telemetryEnabled = enabled
}
}
#endif
25 changes: 25 additions & 0 deletions Sources/Events/EventsClientFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

public class EventsClientFactory {
static func create(
projectId: String,
sdkVersion: String,
storage: EventStorage = UserDefaultsEventStorage()
) -> EventsClient {
let networkingService = NetworkingService(
projectId: projectId,
sdkVersion: sdkVersion
)
let logger = ConsoleLogger(prefix: "🧚🏻‍♂️", loggingLevel: .off)
let retryPolicy = RetryPolicy(maxAttempts: 3, initialDelay: 5, multiplier: 2)
let eventsDispatcher = EventsDispatcher(networkingService: networkingService, retryPolicy: retryPolicy)
let eventsCollector = EventsCollector(storage: storage, logger: logger)
return EventsClient(
eventsCollector: eventsCollector,
eventsDispatcher: eventsDispatcher,
logger: logger,
stateStorage: UserDefaultsTelemetryStateStorage()
)
}
}

Loading

0 comments on commit 5314c59

Please sign in to comment.