fix: more swift stuff
This commit is contained in:
Binary file not shown.
@ -1,24 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
|
||||||
<dependencies>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
||||||
</dependencies>
|
|
||||||
<scenes>
|
|
||||||
<!--Share View Controller-->
|
|
||||||
<scene sceneID="ceB-am-kn3">
|
|
||||||
<objects>
|
|
||||||
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
|
|
||||||
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
||||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
|
||||||
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
|
||||||
</view>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
</scene>
|
|
||||||
</scenes>
|
|
||||||
</document>
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>group.com.haystack.app</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,39 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>Haystack</string>
|
|
||||||
<key>NSExtension</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExtensionAttributes</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExtensionActivationRule</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExtensionActivationSupportsImage</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSExtensionActivationSupportsMovie</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSExtensionActivationSupportsText</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSExtensionActivationSupportsURL</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSExtensionActivationSupportsWebPageWithText</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
<key>NSExtensionActivationSupportsAttachmentsWithMinCount</key>
|
|
||||||
<integer>1</integer>
|
|
||||||
</dict>
|
|
||||||
<key>NSExtensionMainStoryboard</key>
|
|
||||||
<string>MainInterface</string>
|
|
||||||
<key>NSExtensionPointIdentifier</key>
|
|
||||||
<string>com.apple.share-services</string>
|
|
||||||
</dict>
|
|
||||||
<key>NSExtensionPointIdentifier</key>
|
|
||||||
<string>com.apple.ui-services</string>
|
|
||||||
<key>NSExtensionPrincipalClass</key>
|
|
||||||
<string>Haystack.ShareViewController</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,196 +0,0 @@
|
|||||||
//
|
|
||||||
// ShareViewController.swift
|
|
||||||
// Haystack
|
|
||||||
//
|
|
||||||
// Created by Rio Keefe on 03/05/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Social
|
|
||||||
import MobileCoreServices
|
|
||||||
|
|
||||||
class ShareViewController: SLComposeServiceViewController {
|
|
||||||
|
|
||||||
let appGroupName = "group.com.haystack.app" // Replace with your actual App Group identifier
|
|
||||||
let tokenKey = "sharedAuthToken" // This key holds the refresh token.
|
|
||||||
let uploadURL = URL(string: "https://haystack.johncosta.tech/images/")!
|
|
||||||
|
|
||||||
var refreshToken: String?
|
|
||||||
private var imageItemProvider: NSItemProvider?
|
|
||||||
private var extractedImageName: String = "image" // Default name
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
if let sharedDefaults = UserDefaults(suiteName: appGroupName) {
|
|
||||||
refreshToken = sharedDefaults.string(forKey: tokenKey)
|
|
||||||
print("Retrieved refresh token: \(refreshToken ?? "nil")")
|
|
||||||
} else {
|
|
||||||
print("Error accessing App Group UserDefaults.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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[..<dotRange.lowerBound])
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("No image found.")
|
|
||||||
// If no image is found, the content is not valid for this extension
|
|
||||||
// You might want to adjust isContentValid() based on this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func isContentValid() -> Bool {
|
|
||||||
// Content is valid only if we have an item provider for an image AND a bearer token
|
|
||||||
return imageItemProvider != nil && bearerToken != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didSelectPost() {
|
|
||||||
refreshToken {
|
|
||||||
// This method is called when the user taps the "Post" button.
|
|
||||||
// Start the asynchronous operation here.
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
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[..<dotRange.lowerBound])
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if let data = item as? Data {
|
|
||||||
rawImageData = data
|
|
||||||
// Use the suggested name if available, fallback to default
|
|
||||||
finalImageName = provider.suggestedName ?? "image"
|
|
||||||
} else {
|
|
||||||
print("Error: Could not get image data in a usable format.")
|
|
||||||
// Inform the user about the failure
|
|
||||||
self.extensionContext!.cancelRequest(withError: NSError(domain: "ShareExtension", code: -4, userInfo: [NSLocalizedDescriptionKey: "Could not process image data."]))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
guard let dataToUpload = rawImageData else {
|
|
||||||
print("Error: No image data to upload.")
|
|
||||||
// Inform the user about the failure
|
|
||||||
self.extensionContext!.cancelRequest(withError: NSError(domain: "ShareExtension", code: -5, userInfo: [NSLocalizedDescriptionKey: "Image data is missing."]))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now perform the upload asynchronously
|
|
||||||
self.uploadRawData(dataToUpload, imageName: finalImageName, bearerToken: token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not complete the request here.
|
|
||||||
// The request will be completed in the uploadRawData completion handler.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uploadRawData(_ rawData: Data, imageName: String, bearerToken: String) {
|
|
||||||
// bearerToken is guaranteed to be non-nil here due to the guard in didSelectPost
|
|
||||||
|
|
||||||
let uploadURLwithName = uploadURL.appendingPathComponent(imageName)
|
|
||||||
|
|
||||||
var request = URLRequest(url: uploadURLwithName)
|
|
||||||
request.httpMethod = "POST"
|
|
||||||
request.httpBody = rawData
|
|
||||||
request.setValue("application/oclet-stream", forHTTPHeaderField: "Content-Type")
|
|
||||||
request.setValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization")
|
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in
|
|
||||||
// **IMPORTANT:** Complete the extension request on the main thread
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
print("Upload finished. Error: \(error?.localizedDescription ?? "None"), Response: \(response?.description ?? "None")")
|
|
||||||
|
|
||||||
if let error = error {
|
|
||||||
// Handle upload error (e.g., show an alert to the user)
|
|
||||||
print("Upload failed: \(error.localizedDescription)")
|
|
||||||
self?.extensionContext!.cancelRequest(withError: error)
|
|
||||||
} else if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
||||||
// Handle non-success HTTP status codes
|
|
||||||
let errorDescription = "Server returned status code \(httpResponse.statusCode)"
|
|
||||||
print(errorDescription)
|
|
||||||
self?.extensionContext!.cancelRequest(withError: NSError(domain: "ShareExtension", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: errorDescription]))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Upload was successful
|
|
||||||
print("Upload successful")
|
|
||||||
// Complete the request when the upload is done
|
|
||||||
self?.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
task.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 []
|
|
||||||
}
|
|
||||||
}
|
|
@ -49,8 +49,8 @@ class ShareViewController: SLComposeServiceViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func isContentValid() -> Bool {
|
override func isContentValid() -> Bool {
|
||||||
// Content is valid only if we have an item provider for an image AND a bearer token
|
// Content is valid only if we have an item provider for an image AND a refresh token
|
||||||
return imageItemProvider != nil && bearerToken != nil
|
return imageItemProvider != nil && refreshToken != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didSelectPost() {
|
override func didSelectPost() {
|
||||||
|
@ -23,8 +23,8 @@ type BaseRequestParams = Partial<{
|
|||||||
method: "GET" | "POST" | "DELETE";
|
method: "GET" | "POST" | "DELETE";
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const base = "https://haystack.johncosta.tech";
|
// export const base = "https://haystack.johncosta.tech";
|
||||||
// export const base = "http://192.168.1.199:3040";
|
export const base = "http://192.168.1.199:3040";
|
||||||
|
|
||||||
const getBaseRequest = ({ path, body, method }: BaseRequestParams): Request => {
|
const getBaseRequest = ({ path, body, method }: BaseRequestParams): Request => {
|
||||||
return new Request(`${base}/${path}`, {
|
return new Request(`${base}/${path}`, {
|
||||||
|
Reference in New Issue
Block a user