Skip to content

Remove Alamofire and fix Swift 6 crash #15

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

Merged
merged 1 commit into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Required for use in Swift 6

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.