Created
August 18, 2025 17:49
-
-
Save julianfbeck/babe0b27cdacd52dc2d8dc5fd8dd57fb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Foundation | |
| import UIKit | |
| public enum PlausibleError: Error { | |
| case domainNotSet | |
| case invalidDomain | |
| case eventIsPageview | |
| } | |
| public class Plausible { | |
| public private(set) var endpoint = "" | |
| public private(set) var domain = "" | |
| public static let shared = Plausible() | |
| private let queue = DispatchQueue(label: "com.plausibleswift.queue", qos: .utility) | |
| private let userDefaults = UserDefaults.standard | |
| private let appOpenCountKey = "com.plausibleswift.appOpenCount" | |
| private init() {} | |
| public func configure(domain: String, endpoint: String) { | |
| self.endpoint = endpoint | |
| self.domain = domain | |
| let appOpenCount = incrementAppOpenCount() | |
| DispatchQueue.main.async { [weak self] in | |
| guard let self = self else { return } | |
| var deviceInfo = self.gatherDeviceInfo() | |
| deviceInfo["app_open_count"] = String(appOpenCount) | |
| self.trackEvent(event: "open", path: "/open", properties: deviceInfo) | |
| if appOpenCount == 1 { | |
| self.trackEvent(event: "install", path: "/install") | |
| } | |
| } | |
| } | |
| public func trackPageview(path: String, properties: [String: String] = [:]) { | |
| queue.async { [weak self] in | |
| guard let self = self, self.domain != "" else { return } | |
| Task { | |
| do { | |
| try await self.plausibleRequest(name: "pageview", path: path, properties: properties) | |
| } catch { | |
| print("Plausible error: \(error)") | |
| } | |
| } | |
| } | |
| } | |
| public func trackEvent(event: String, path: String, properties: [String: String] = [:]) { | |
| queue.async { [weak self] in | |
| guard let self = self, event != "pageview" else { return } | |
| Task { | |
| do { | |
| try await self.plausibleRequest(name: event, path: path, properties: properties) | |
| } catch { | |
| print("Plausible error: \(error)") | |
| } | |
| } | |
| } | |
| } | |
| private func plausibleRequest(name: String, path: String, properties: [String: String]) async throws { | |
| guard let plausibleEventURL = URL(string: self.endpoint) else { | |
| throw PlausibleError.invalidDomain | |
| } | |
| var req = URLRequest(url: plausibleEventURL) | |
| req.httpMethod = "POST" | |
| req.setValue("application/json", forHTTPHeaderField: "Content-Type") | |
| var jsonObject: [String: Any] = ["name": name, "url": constructPageviewURL(path: path), "domain": domain] | |
| if !properties.isEmpty { | |
| jsonObject["props"] = properties | |
| } | |
| let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject) | |
| req.httpBody = jsonData | |
| do { | |
| let (_, _) = try await URLSession.shared.data(for: req) | |
| } catch { | |
| print("Plausible network error: \(error)") | |
| } | |
| } | |
| private func constructPageviewURL(path: String) -> String { | |
| let url = URL(string: "https://\(domain)")! | |
| return url.appendingPathComponent(path).absoluteString | |
| } | |
| private func gatherDeviceInfo() -> [String: String] { | |
| let device = UIDevice.current | |
| let screenSize = UIScreen.main.bounds.size | |
| let locale = Locale.current | |
| var info: [String: String] = [ | |
| "os_version": device.systemVersion, | |
| "device_model": device.model, | |
| "device_name": device.name, | |
| "screen_width": String(format: "%.0f", screenSize.width), | |
| "screen_height": String(format: "%.0f", screenSize.height), | |
| "locale": locale.identifier, | |
| "language": locale.languageCode ?? "unknown" | |
| ] | |
| if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { | |
| info["app_version"] = appVersion | |
| } | |
| if let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String { | |
| info["build_number"] = buildNumber | |
| } | |
| return info | |
| } | |
| private func incrementAppOpenCount() -> Int { | |
| let currentCount = userDefaults.integer(forKey: appOpenCountKey) | |
| let newCount = currentCount + 1 | |
| userDefaults.set(newCount, forKey: appOpenCountKey) | |
| return newCount | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment