From 72de7c76482fe382e8f6f9cd0b3e530fffe77ca0 Mon Sep 17 00:00:00 2001 From: John Costa Date: Sun, 5 Oct 2025 20:31:59 +0100 Subject: [PATCH] fix: swift --- .../apple/Haystack/ShareViewController.swift | 163 ++++---- .../apple/Sharing/ShareViewController.swift | 380 +++++++----------- 2 files changed, 242 insertions(+), 301 deletions(-) diff --git a/frontend/src-tauri/gen/apple/Haystack/ShareViewController.swift b/frontend/src-tauri/gen/apple/Haystack/ShareViewController.swift index 3fc28eb..ed608ab 100644 --- a/frontend/src-tauri/gen/apple/Haystack/ShareViewController.swift +++ b/frontend/src-tauri/gen/apple/Haystack/ShareViewController.swift @@ -12,26 +12,21 @@ import MobileCoreServices class ShareViewController: SLComposeServiceViewController { let appGroupName = "group.com.haystack.app" // Replace with your actual App Group identifier - let tokenKey = "sharedAuthToken" - let uploadURL = URL(string: "https://haystack.johncosta.tech/image/")! + let tokenKey = "sharedAuthToken" // This key holds the refresh token. + let uploadURL = URL(string: "https://haystack.johncosta.tech/images/")! - var bearerToken: String? - // Store the item provider to access it later in didSelectPost + var refreshToken: String? private var imageItemProvider: NSItemProvider? private var extractedImageName: String = "image" // Default name override func viewDidLoad() { super.viewDidLoad() - // Load the bearer token from the App Group in viewDidLoad - // This is okay as reading from UserDefaults is fast if let sharedDefaults = UserDefaults(suiteName: appGroupName) { - bearerToken = sharedDefaults.string(forKey: tokenKey) - print("Retrieved bearer token: \(bearerToken ?? "nil")") + refreshToken = sharedDefaults.string(forKey: tokenKey) + print("Retrieved refresh token: \(refreshToken ?? "nil")") } else { print("Error accessing App Group UserDefaults.") - // Optionally inform the user or disable posting if token is crucial - // self.isContentValid() could check if bearerToken is nil } // Store the item provider, but don't load the data synchronously yet @@ -59,70 +54,72 @@ class ShareViewController: SLComposeServiceViewController { } override func didSelectPost() { - // This method is called when the user taps the "Post" button. - // Start the asynchronous operation here. + refreshToken { + // This method is called when the user taps the "Post" button. + // Start the asynchronous operation here. - guard let provider = imageItemProvider else { - print("Error: No image item provider found when posting.") - // Inform the user or log an error - extensionContext!.completeRequest(returningItems: [], completionHandler: nil) - return - } - - guard let token = bearerToken else { - print("Error: Bearer token is missing when posting.") - // Inform the user or log an error - extensionContext!.completeRequest(returningItems: [], completionHandler: nil) - return - } - - // Load the image data asynchronously - provider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { [weak self] (item, error) in - guard let self = self else { return } - - if let error = error { - print("Error loading image data for upload: \(error.localizedDescription)") - // Inform the user about the failure - self.extensionContext!.cancelRequest(withError: error) + guard let provider = self.imageItemProvider else { + print("Error: No image item provider found when posting.") + // Inform the user or log an error + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) return } - var rawImageData: Data? - var finalImageName = self.extractedImageName // Use the name extracted earlier - - if let url = item as? URL, let data = try? Data(contentsOf: url) { - rawImageData = data - // Refine the name extraction here if necessary, though doing it in viewDidLoad is also an option - finalImageName = url.lastPathComponent - if let dotRange = finalImageName.range(of: ".", options: .backwards) { - finalImageName = String(finalImageName[.. [Any]! { - // You can add items here if you want to allow the user to enter additional info - // e.g., a text field for a caption. - // This example only handles image upload, so no config items are needed. - return [] + func refreshToken(completion: @escaping () -> Void) { + let url = URL(string: "https://haystack.johncosta.tech/auth/refresh")! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let body = ["refresh": refreshToken] + request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: []) + + let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in + guard let self = self else { return } + + if let data = data, + let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let accessToken = json["access"] as? String { + + if let sharedDefaults = UserDefaults(suiteName: self.appGroupName) { + sharedDefaults.set(accessToken, forKey: self.tokenKey) + self.bearerToken = accessToken + } + } + + completion() + } + + task.resume() + } + + override func configurationItems() -> [Any]! { + // You can add items here if you want to allow the user to enter additional info + // e.g., a text field for a caption. + // This example only handles image upload, so no config items are needed. + return [] } } diff --git a/frontend/src-tauri/gen/apple/Sharing/ShareViewController.swift b/frontend/src-tauri/gen/apple/Sharing/ShareViewController.swift index d7447dc..ed608ab 100644 --- a/frontend/src-tauri/gen/apple/Sharing/ShareViewController.swift +++ b/frontend/src-tauri/gen/apple/Sharing/ShareViewController.swift @@ -1,280 +1,196 @@ // -// ShareViewController.swift -// Haystack +//  ShareViewController.swift +//  Haystack // -// Created by Rio Keefe on 03/05/2025. +//  Created by Rio Keefe on 03/05/2025. // import UIKit import Social -import MobileCoreServices // For kUTTypeImage +import MobileCoreServices class ShareViewController: SLComposeServiceViewController { let appGroupName = "group.com.haystack.app" // Replace with your actual App Group identifier - let tokenKey = "sharedAuthToken" - let uploadURLBase = URL(string: "https://haystack.johncosta.tech/image/")! // Base URL + let tokenKey = "sharedAuthToken" // This key holds the refresh token. + let uploadURL = URL(string: "https://haystack.johncosta.tech/images/")! - var bearerToken: String? + var refreshToken: String? private var imageItemProvider: NSItemProvider? - // Store a base name, extension will be determined during item loading - private var baseImageName: String = "SharedImage" // A more descriptive default + private var extractedImageName: String = "image" // Default name override func viewDidLoad() { super.viewDidLoad() if let sharedDefaults = UserDefaults(suiteName: appGroupName) { - bearerToken = sharedDefaults.string(forKey: tokenKey) - print("Retrieved bearer token: \(bearerToken ?? "nil")") + refreshToken = sharedDefaults.string(forKey: tokenKey) + print("Retrieved refresh token: \(refreshToken ?? "nil")") } else { print("Error accessing App Group UserDefaults.") - // Invalidate content if token is crucial and missing - // This will be caught by isContentValid() } - guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, - let provider = extensionItem.attachments?.first else { - print("No attachments found.") - // Invalidate content if no provider - self.imageItemProvider = nil // Ensure it's nil so isContentValid fails - return - } + // Store the item provider, but don't load the data synchronously yet + if let item = extensionContext?.inputItems.first as? NSExtensionItem, + let provider = item.attachments?.first as? NSItemProvider { + if provider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { + self.imageItemProvider = provider + // Attempt to get a suggested name early if available + extractedImageName = provider.suggestedName ?? "image" + if let dotRange = extractedImageName.range(of: ".", options: .backwards) { + extractedImageName = String(extractedImageName[..:") - self.baseImageName = self.baseImageName.components(separatedBy: anInvalidCharacters).joined(separator: "_") - if self.baseImageName.isEmpty { self.baseImageName = "SharedImage" } // Ensure not empty - - } else { - print("Attachment is not an image.") - self.imageItemProvider = nil // Ensure it's nil so isContentValid fails } } override func isContentValid() -> Bool { // Content is valid only if we have an item provider for an image AND a bearer token - let isValid = imageItemProvider != nil && bearerToken != nil - if imageItemProvider == nil { - print("isContentValid: imageItemProvider is nil") - } - if bearerToken == nil { - print("isContentValid: bearerToken is nil") - } - return isValid + return imageItemProvider != nil && bearerToken != nil } override func didSelectPost() { - guard let provider = imageItemProvider else { - print("Error: No image item provider found when posting.") - informUserAndCancel(message: "No image found to share.") - return - } + refreshToken { + // This method is called when the user taps the "Post" button. + // Start the asynchronous operation here. - guard let token = bearerToken else { - print("Error: Bearer token is missing when posting.") - informUserAndCancel(message: "Authentication error. Please try again.") - return - } - - // Start activity indicator or similar UI feedback - // For SLComposeServiceViewController, the system provides some UI - - provider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { [weak self] (item, error) in - guard let self = self else { return } - - if let error = error { - print("Error loading image data for upload: \(error.localizedDescription)") - self.informUserAndCancel(message: "Could not load image: \(error.localizedDescription)") + guard let provider = self.imageItemProvider else { + print("Error: No image item provider found when posting.") + // Inform the user or log an error + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) return } - var imageData: Data? - var finalImageNameWithExtension: String - var mimeType: String = "application/octet-stream" // Default MIME type - - // Determine base name (without extension) - var currentBaseName = self.baseImageName // Use the one prepared in viewDidLoad - if let suggested = provider.suggestedName, !suggested.isEmpty { - if let dotRange = suggested.range(of: ".", options: .backwards) { - currentBaseName = String(suggested[..:") - currentBaseName = currentBaseName.components(separatedBy: anInvalidCharacters).joined(separator: "_") - if currentBaseName.isEmpty { currentBaseName = "DefaultImageName" } + guard let token = self.bearerToken else { + print("Error: Bearer token is missing when posting.") + // Inform the user or log an error + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + return } - - if let url = item as? URL { - print("Image provided as URL: \(url)") - finalImageNameWithExtension = url.lastPathComponent // Includes extension - // Ensure baseName is updated if URL provides a different one - if let dotRange = finalImageNameWithExtension.range(of:".", options: .backwards) { - currentBaseName = String(finalImageNameWithExtension[.. Void) { + let url = URL(string: "https://haystack.johncosta.tech/auth/refresh")! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let body = ["refresh": refreshToken] + request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: []) + + let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in + guard let self = self else { return } + + if let data = data, + let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let accessToken = json["access"] as? String { + + if let sharedDefaults = UserDefaults(suiteName: self.appGroupName) { + sharedDefaults.set(accessToken, forKey: self.tokenKey) + self.bearerToken = accessToken + } + } + + completion() + } + task.resume() } - - override func configurationItems() -> [Any]! { - // No configuration items needed for this simple image uploader. - return [] - } - - // Helper to inform user and cancel request - private func informUserAndCancel(message: String) { - let error = NSError(domain: "com.haystack.ShareExtension", code: 0, userInfo: [NSLocalizedDescriptionKey: message]) - print("Informing user: \(message)") - // You could present an alert here if SLComposeServiceViewController allows easy alert presentation. - // For now, just cancel the request. The system might show a generic error. - self.extensionContext!.cancelRequest(withError: error) - } - - // Helper to get MIME type from path extension - private func mimeType(forPathExtension pathExtension: String) -> String { - let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue() - if let uti = uti { - let mimeType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() - if let mimeType = mimeType { - return mimeType as String - } - } - // Fallback for common types if UTType fails or for robustness - switch pathExtension.lowercased() { - // case "jpg", "jpeg": return "image/jpeg" - // case "png": return "image/png" - // case "gif": return "image/gif" - // case "bmp": return "image/bmp" - // case "tiff", "tif": return "image/tiff" - default: return "application/octet-stream" // Generic fallback - } + + override func configurationItems() -> [Any]! { + // You can add items here if you want to allow the user to enter additional info + // e.g., a text field for a caption. + // This example only handles image upload, so no config items are needed. + return [] } }