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

Events sdk #1380

Merged
merged 21 commits into from
Jul 9, 2024
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>
9 changes: 9 additions & 0 deletions Example/DApp/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
order: 1,
mobileLink: "walletapp://",
linkMode: "https://lab.web3modal.com/wallet"
),
.init(
id: "rn-sample",
name: "RN Sample Wallet",
homepage: "https://walletconnect.com/",
imageUrl: "https://avatars.githubusercontent.com/u/37784886?s=200&v=4",
order: 1,
mobileLink: "rn-web3wallet://",
linkMode: "https://lab.web3modal.com/walletkit_rn"
)
]
)
Expand Down
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
Loading