676 lines · 25.0 KB · Swift
RawDownload
import Foundation #if os(macOS) // MARK: - Factory Cookie Importer /// Imports Factory session cookies from Safari/Chrome/Firefox browserspublic enum FactoryCookieImporter {    private static let sessionCookieNames: Set<String> = [        "wos-session",        "__Secure-next-auth.session-token",        "next-auth.session-token",        "__Host-authjs.csrf-token",        "authjs.session-token",    ]     public struct SessionInfo: Sendable {        public let cookies: [HTTPCookie]        public let sourceLabel: String         public init(cookies: [HTTPCookie], sourceLabel: String) {            self.cookies = cookies            self.sourceLabel = sourceLabel        }         public var cookieHeader: String {            self.cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")        }    }     /// Attempts to import Factory cookies from Safari first, then Chrome, then Firefox    public static func importSession(logger: ((String) -> Void)? = nil) throws -> SessionInfo {        let log: (String) -> Void = { msg in logger?("[factory-cookie] \(msg)") }         // Try Safari first        do {            let safariRecords = try SafariCookieImporter.loadCookies(                matchingDomains: ["factory.ai", "app.factory.ai", "auth.factory.ai"],                logger: log)            if !safariRecords.isEmpty {                let httpCookies = SafariCookieImporter.makeHTTPCookies(safariRecords)                if httpCookies.contains(where: { Self.sessionCookieNames.contains($0.name) }) {                    log("Found \(httpCookies.count) Factory cookies in Safari")                    return SessionInfo(cookies: httpCookies, sourceLabel: "Safari")                } else {                    log("Safari cookies found, but no Factory session cookie present")                }            }        } catch {            log("Safari cookie import failed: \(error.localizedDescription)")        }         // Try Chrome        do {            let chromeSources = try ChromeCookieImporter.loadCookiesFromAllProfiles(                matchingDomains: ["factory.ai", "app.factory.ai", "auth.factory.ai"])            for source in chromeSources where !source.records.isEmpty {                let httpCookies = source.records.compactMap { record -> HTTPCookie? in                    let domain = record.hostKey.hasPrefix(".") ? String(record.hostKey.dropFirst()) : record.hostKey                    var props: [HTTPCookiePropertyKey: Any] = [                        .domain: domain,                        .path: record.path,                        .name: record.name,                        .value: record.value,                        .secure: record.isSecure,                    ]                    if record.isHTTPOnly {                        props[.init("HttpOnly")] = "TRUE"                    }                    if record.expiresUTC > 0 {                        let unixTimestamp = Double(record.expiresUTC - 11_644_473_600_000_000) / 1_000_000                        props[.expires] = Date(timeIntervalSince1970: unixTimestamp)                    }                    return HTTPCookie(properties: props)                }                if httpCookies.contains(where: { Self.sessionCookieNames.contains($0.name) }) {                    log("Found \(httpCookies.count) Factory cookies in \(source.label)")                    return SessionInfo(cookies: httpCookies, sourceLabel: source.label)                } else {                    log("Chrome source \(source.label) has no Factory session cookie")                }            }        } catch {            log("Chrome cookie import failed: \(error.localizedDescription)")        }         // Try Firefox        do {            let firefoxSources = try FirefoxCookieImporter.loadCookiesFromAllProfiles(                matchingDomains: ["factory.ai", "app.factory.ai", "auth.factory.ai"])            for source in firefoxSources where !source.records.isEmpty {                let httpCookies = source.records.compactMap { record -> HTTPCookie? in                    let domain = record.host.hasPrefix(".") ? String(record.host.dropFirst()) : record.host                    var props: [HTTPCookiePropertyKey: Any] = [                        .domain: domain,                        .path: record.path,                        .name: record.name,                        .value: record.value,                        .secure: record.isSecure,                    ]                    if record.isHTTPOnly {                        props[.init("HttpOnly")] = "TRUE"                    }                    if let expires = record.expires {                        props[.expires] = expires                    }                    return HTTPCookie(properties: props)                }                if httpCookies.contains(where: { Self.sessionCookieNames.contains($0.name) }) {                    log("Found \(httpCookies.count) Factory cookies in \(source.label)")                    return SessionInfo(cookies: httpCookies, sourceLabel: source.label)                } else {                    log("Firefox source \(source.label) has no Factory session cookie")                }            }        } catch {            log("Firefox cookie import failed: \(error.localizedDescription)")        }         throw FactoryStatusProbeError.noSessionCookie    }     /// Check if Factory session cookies are available    public static func hasSession(logger: ((String) -> Void)? = nil) -> Bool {        do {            let session = try self.importSession(logger: logger)            return !session.cookies.isEmpty        } catch {            return false        }    }} // MARK: - Factory API Models public struct FactoryAuthResponse: Codable, Sendable {    public let featureFlags: FactoryFeatureFlags?    public let organization: FactoryOrganization?} public struct FactoryFeatureFlags: Codable, Sendable {    public let flags: [String: Bool]?    public let configs: [String: AnyCodable]?} public struct FactoryOrganization: Codable, Sendable {    public let id: String?    public let name: String?    public let subscription: FactorySubscription?     enum CodingKeys: String, CodingKey {        case id        case name        case subscription    }} public struct FactorySubscription: Codable, Sendable {    public let factoryTier: String?    public let orbSubscription: FactoryOrbSubscription?} public struct FactoryOrbSubscription: Codable, Sendable {    public let plan: FactoryPlan?    public let status: String?} public struct FactoryPlan: Codable, Sendable {    public let name: String?    public let id: String?} public struct FactoryUsageResponse: Codable, Sendable {    public let usage: FactoryUsageData?    public let source: String?    public let userId: String?} public struct FactoryUsageData: Codable, Sendable {    public let startDate: Int64?    public let endDate: Int64?    public let standard: FactoryTokenUsage?    public let premium: FactoryTokenUsage?} public struct FactoryTokenUsage: Codable, Sendable {    public let userTokens: Int64?    public let orgTotalTokensUsed: Int64?    public let totalAllowance: Int64?    public let usedRatio: Double?    public let orgOverageUsed: Int64?    public let basicAllowance: Int64?    public let orgOverageLimit: Int64?} /// Helper for encoding arbitrary JSONpublic struct AnyCodable: Codable, Sendable {    public init(from decoder: Decoder) throws {        let container = try decoder.singleValueContainer()        if container.decodeNil() {            return        }        _ = try? container.decode([String: AnyCodable].self)    }     public func encode(to encoder: Encoder) throws {        var container = encoder.singleValueContainer()        try container.encodeNil()    }} // MARK: - Factory Status Snapshot public struct FactoryStatusSnapshot: Sendable {    /// Standard token usage (user)    public let standardUserTokens: Int64    /// Standard token usage (org total)    public let standardOrgTokens: Int64    /// Standard token allowance    public let standardAllowance: Int64    /// Premium token usage (user)    public let premiumUserTokens: Int64    /// Premium token usage (org total)    public let premiumOrgTokens: Int64    /// Premium token allowance    public let premiumAllowance: Int64    /// Billing period start    public let periodStart: Date?    /// Billing period end    public let periodEnd: Date?    /// Plan name    public let planName: String?    /// Factory tier (enterprise, team, etc.)    public let tier: String?    /// Organization name    public let organizationName: String?    /// User email    public let accountEmail: String?    /// User ID    public let userId: String?    /// Raw JSON for debugging    public let rawJSON: String?     public init(        standardUserTokens: Int64,        standardOrgTokens: Int64,        standardAllowance: Int64,        premiumUserTokens: Int64,        premiumOrgTokens: Int64,        premiumAllowance: Int64,        periodStart: Date?,        periodEnd: Date?,        planName: String?,        tier: String?,        organizationName: String?,        accountEmail: String?,        userId: String?,        rawJSON: String?)    {        self.standardUserTokens = standardUserTokens        self.standardOrgTokens = standardOrgTokens        self.standardAllowance = standardAllowance        self.premiumUserTokens = premiumUserTokens        self.premiumOrgTokens = premiumOrgTokens        self.premiumAllowance = premiumAllowance        self.periodStart = periodStart        self.periodEnd = periodEnd        self.planName = planName        self.tier = tier        self.organizationName = organizationName        self.accountEmail = accountEmail        self.userId = userId        self.rawJSON = rawJSON    }     /// Convert to UsageSnapshot for the common provider interface    public func toUsageSnapshot() -> UsageSnapshot {        // Primary: Standard tokens used (as percentage of allowance, capped reasonably)        let standardPercent = self.calculateUsagePercent(            used: self.standardUserTokens,            allowance: self.standardAllowance)         let primary = RateWindow(            usedPercent: standardPercent,            windowMinutes: nil,            resetsAt: self.periodEnd,            resetDescription: self.periodEnd.map { Self.formatResetDate($0) })         // Secondary: Premium tokens used        let premiumPercent = self.calculateUsagePercent(            used: self.premiumUserTokens,            allowance: self.premiumAllowance)         let secondary = RateWindow(            usedPercent: premiumPercent,            windowMinutes: nil,            resetsAt: self.periodEnd,            resetDescription: self.periodEnd.map { Self.formatResetDate($0) })         // Format login method as tier + plan        let loginMethod: String? = {            var parts: [String] = []            if let tier = self.tier, !tier.isEmpty {                parts.append("Factory \(tier.capitalized)")            }            if let plan = self.planName, !plan.isEmpty, !plan.lowercased().contains("factory") {                parts.append(plan)            }            return parts.isEmpty ? nil : parts.joined(separator: " - ")        }()         return UsageSnapshot(            primary: primary,            secondary: secondary,            tertiary: nil,            providerCost: nil,            updatedAt: Date(),            accountEmail: self.accountEmail,            accountOrganization: self.organizationName,            loginMethod: loginMethod)    }     private func calculateUsagePercent(used: Int64, allowance: Int64) -> Double {        // Treat very large allowances (> 1 trillion) as unlimited        let unlimitedThreshold: Int64 = 1_000_000_000_000        if allowance > unlimitedThreshold {            // For unlimited, show a token count-based pseudo-percentage (capped at 100%)            // Use 100M tokens as a reference point for "100%"            let referenceTokens: Double = 100_000_000            return min(100, Double(used) / referenceTokens * 100)        }        guard allowance > 0 else { return 0 }        return min(100, Double(used) / Double(allowance) * 100)    }     private static func formatResetDate(_ date: Date) -> String {        let formatter = DateFormatter()        formatter.dateFormat = "MMM d 'at' h:mma"        formatter.locale = Locale(identifier: "en_US_POSIX")        return "Resets " + formatter.string(from: date)    }} // MARK: - Factory Status Probe Error public enum FactoryStatusProbeError: LocalizedError, Sendable {    case notLoggedIn    case networkError(String)    case parseFailed(String)    case noSessionCookie     public var errorDescription: String? {        switch self {        case .notLoggedIn:            "Not logged in to Factory. Please log in via the CodexBar menu."        case let .networkError(msg):            "Factory API error: \(msg)"        case let .parseFailed(msg):            "Could not parse Factory usage: \(msg)"        case .noSessionCookie:            "No Factory session found. Please log in to app.factory.ai in Safari, Chrome, or Firefox."        }    }} // MARK: - Factory Session Store public actor FactorySessionStore {    public static let shared = FactorySessionStore()     private var sessionCookies: [HTTPCookie] = []    private var bearerToken: String?    private let fileURL: URL     private init() {        let fm = FileManager.default        let appSupport = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first            ?? fm.temporaryDirectory        let dir = appSupport.appendingPathComponent("CodexBar", isDirectory: true)        try? fm.createDirectory(at: dir, withIntermediateDirectories: true)        self.fileURL = dir.appendingPathComponent("factory-session.json")         Task { await self.loadFromDisk() }    }     public func setCookies(_ cookies: [HTTPCookie]) {        self.sessionCookies = cookies        self.saveToDisk()    }     public func getCookies() -> [HTTPCookie] {        self.sessionCookies    }     public func setBearerToken(_ token: String?) {        self.bearerToken = token    }     public func getBearerToken() -> String? {        self.bearerToken    }     public func clearSession() {        self.sessionCookies = []        self.bearerToken = nil        try? FileManager.default.removeItem(at: self.fileURL)    }     public func hasValidSession() -> Bool {        !self.sessionCookies.isEmpty || self.bearerToken != nil    }     private func saveToDisk() {        let cookieData = self.sessionCookies.compactMap { cookie -> [String: Any]? in            guard let props = cookie.properties else { return nil }            var serializable: [String: Any] = [:]            for (key, value) in props {                let keyString = key.rawValue                if let date = value as? Date {                    serializable[keyString] = date.timeIntervalSince1970                    serializable[keyString + "_isDate"] = true                } else if let url = value as? URL {                    serializable[keyString] = url.absoluteString                    serializable[keyString + "_isURL"] = true                } else if JSONSerialization.isValidJSONObject([value]) ||                    value is String ||                    value is Bool ||                    value is NSNumber                {                    serializable[keyString] = value                }            }            return serializable        }        guard !cookieData.isEmpty,              let data = try? JSONSerialization.data(withJSONObject: cookieData, options: [.prettyPrinted])        else {            return        }        try? data.write(to: self.fileURL)    }     private func loadFromDisk() {        guard let data = try? Data(contentsOf: self.fileURL),              let cookieArray = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]]        else { return }         self.sessionCookies = cookieArray.compactMap { props in            var cookieProps: [HTTPCookiePropertyKey: Any] = [:]            for (key, value) in props {                if key.hasSuffix("_isDate") || key.hasSuffix("_isURL") { continue }                 let propKey = HTTPCookiePropertyKey(key)                 if props[key + "_isDate"] as? Bool == true, let interval = value as? TimeInterval {                    cookieProps[propKey] = Date(timeIntervalSince1970: interval)                } else if props[key + "_isURL"] as? Bool == true, let urlString = value as? String {                    cookieProps[propKey] = URL(string: urlString)                } else {                    cookieProps[propKey] = value                }            }            return HTTPCookie(properties: cookieProps)        }    }} // MARK: - Factory Status Probe public struct FactoryStatusProbe: Sendable {    public let baseURL: URL    public var timeout: TimeInterval = 15.0     public init(baseURL: URL = URL(string: "https://app.factory.ai")!, timeout: TimeInterval = 15.0) {        self.baseURL = baseURL        self.timeout = timeout    }     /// Fetch Factory usage using browser cookies (Safari/Chrome/Firefox) with fallback to stored session    public func fetch(logger: ((String) -> Void)? = nil) async throws -> FactoryStatusSnapshot {        let log: (String) -> Void = { msg in logger?("[factory] \(msg)") }         // Try importing cookies from Safari/Chrome/Firefox first        do {            let session = try FactoryCookieImporter.importSession(logger: log)            log("Using cookies from \(session.sourceLabel)")            let snapshot = try await self.fetchWithCookieHeader(session.cookieHeader)            await FactorySessionStore.shared.setCookies(session.cookies)            return snapshot        } catch {            log("Browser cookie import failed: \(error.localizedDescription)")        }         // Fall back to stored session cookies        let storedCookies = await FactorySessionStore.shared.getCookies()        if !storedCookies.isEmpty {            log("Using stored session cookies")            let cookieHeader = storedCookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")            do {                return try await self.fetchWithCookieHeader(cookieHeader)

Lines 1–500 of 676.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t