Created
August 18, 2025 17:49
-
-
Save julianfbeck/babe0b27cdacd52dc2d8dc5fd8dd57fb to your computer and use it in GitHub Desktop.
Revisions
-
julianfbeck created this gist
Aug 18, 2025 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,130 @@ 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 } }