Skip to content

Commit

Permalink
Merge pull request #15 from maxhumber/main
Browse files Browse the repository at this point in the history
Remove Alamofire and fix Swift 6 crash
  • Loading branch information
roelvanderkraan authored Jan 4, 2025
2 parents 5247eed + 11ef015 commit df261f5
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 98 deletions.
16 changes: 0 additions & 16 deletions Package.resolved

This file was deleted.

25 changes: 3 additions & 22 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,10 @@ let package = Package(
.iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macOS(.v10_15)
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "SimpleAnalytics",
targets: ["SimpleAnalytics"]),
.library(name: "SimpleAnalytics", targets: ["SimpleAnalytics"])
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.8.1")),
],

targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "SimpleAnalytics",
dependencies: [
.product(
name: "Alamofire",
package: "Alamofire"
),
],
path: "Sources"),
.testTarget(
name: "SimpleAnalyticsTests",
dependencies: ["SimpleAnalytics"]),
.target(name: "SimpleAnalytics", path: "Sources"),
.testTarget(name: "SimpleAnalyticsTests", dependencies: ["SimpleAnalytics"])
]
)
49 changes: 9 additions & 40 deletions Sources/SimpleAnalytics/RequestDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,19 @@
//

import Foundation
import Alamofire

internal struct RequestDispatcher {
/// Sends the event to Simple Analytics
/// - Parameter event: the event to dispatch
static internal func sendEventRequest(event: Event) async throws {
return try await withCheckedThrowingContinuation { continuation in
AF.request("https://queue.simpleanalyticscdn.com/events",
method: .post,
parameters: event,
encoder: JSONParameterEncoder.default).responseData { response in

switch(response.result) {
case .success(_):
continuation.resume()
case let .failure(error):
continuation.resume(throwing: self.handleError(error: error))
}
}
}
guard let url = URL(string: "https://queue.simpleanalyticscdn.com/events") else { throw URLError(.badURL) }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let jsonData = try JSONEncoder().encode(event)
request.httpBody = jsonData
let (_, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) }
guard (200...299).contains(httpResponse.statusCode) else { throw URLError(URLError.Code(rawValue: httpResponse.statusCode)) }
}

static private func handleError(error: AFError) -> Error {
if let underlyingError = error.underlyingError {
let nserror = underlyingError as NSError
let code = nserror.code
if code == NSURLErrorNotConnectedToInternet ||
code == NSURLErrorTimedOut ||
code == NSURLErrorInternationalRoamingOff ||
code == NSURLErrorDataNotAllowed ||
code == NSURLErrorCannotFindHost ||
code == NSURLErrorCannotConnectToHost ||
code == NSURLErrorNetworkConnectionLost
{
var userInfo = nserror.userInfo
userInfo[NSLocalizedDescriptionKey] = "Unable to connect to the server"
let currentError = NSError(
domain: nserror.domain,
code: code,
userInfo: userInfo
)
return currentError
}
}
return error
}
}
15 changes: 11 additions & 4 deletions Sources/SimpleAnalytics/SimpleAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ import Foundation
final public class SimpleAnalytics: NSObject {
/// The hostname of the website in Simple Analytics the tracking should be send to. Without `https://`
let hostname: String
private let userAgent: String
private var userAgent: String?
private let userLanguage: String
private let userTimezone: String
/// The last date a unique visit was tracked.
private var visitDate: Date?
private let userAgentProvider = UserAgentProvider()
private var sharedDefaultsSuiteName: String?

/// Defines if the user is opted out. When set to `true`, all tracking will be skipped. This is persisted between sessions.
Expand All @@ -47,7 +46,6 @@ final public class SimpleAnalytics: NSObject {
/// - Parameter hostname: The hostname as found in SimpleAnalytics, without `https://`
public init(hostname: String) {
self.hostname = hostname
self.userAgent = userAgentProvider.userAgent
self.userLanguage = Locale.current.identifier
self.userTimezone = TimeZone.current.identifier
self.visitDate = UserDefaults.standard.object(forKey: Keys.visitDateKey) as? Date
Expand All @@ -58,7 +56,6 @@ final public class SimpleAnalytics: NSObject {
/// - Parameter: sharedDefaultsSuiteName: When extensions (such as a main app and widget) have a set of sharedDefaults (using an App Group) that unique user can be counted once using this (instead of two or more times when using app and widget, etc.)
public init(hostname: String, sharedDefaultsSuiteName: String) {
self.hostname = hostname
self.userAgent = userAgentProvider.userAgent
self.userLanguage = Locale.current.identifier
self.userTimezone = TimeZone.current.identifier
self.sharedDefaultsSuiteName = sharedDefaultsSuiteName
Expand Down Expand Up @@ -96,6 +93,7 @@ final public class SimpleAnalytics: NSObject {
guard !isOptedOut else {
return
}
let userAgent = try await getUserAgent()
let event = Event(
type: .pageview,
hostname: hostname,
Expand All @@ -115,6 +113,7 @@ final public class SimpleAnalytics: NSObject {
guard !isOptedOut else {
return
}
let userAgent = try await getUserAgent()
let event = Event(
type: .event,
hostname: hostname,
Expand Down Expand Up @@ -188,6 +187,14 @@ final public class SimpleAnalytics: NSObject {
}
}

/// Get the cached userAgent or fetch a new one
internal func getUserAgent() async throws -> String {
if let userAgent { return userAgent }
let newUserAgent = try await UserAgentFetcher.fetch()
userAgent = newUserAgent
return newUserAgent
}

/// Keys used to store things in UserDefaults
internal struct Keys {
static let visitDateKey = "simpleanalytics.visitdate"
Expand Down
21 changes: 21 additions & 0 deletions Sources/SimpleAnalytics/UserAgentFetcher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// UserAgent.swift
//
//
// Created by Max Humber on 2025-01-02.

import WebKit

enum UserAgentFetcher {
@MainActor
static func fetch() async throws -> String {
let webView = WKWebView(frame: .zero)
let result = try await webView.evaluateJavaScript("navigator.userAgent")
guard let userAgent = result as? String else { throw UserAgentError.unableToFetchUserAgent }
return userAgent
}
}

enum UserAgentError: Error {
case unableToFetchUserAgent
}
16 changes: 0 additions & 16 deletions Sources/SimpleAnalytics/UserAgentProvider.swift

This file was deleted.

0 comments on commit df261f5

Please sign in to comment.