Write shareExtensionLastStart on ShareViewController.viewDidLoad and expose getShareExtensionDiagnostics() through SharedImagePlugin with shareId, file path, and fileExists for debugging failed share flows.
164 lines
6.5 KiB
Swift
164 lines
6.5 KiB
Swift
//
|
|
// SharedImageUtility.swift
|
|
// App
|
|
//
|
|
// Shared utility for accessing shared image data from App Group container
|
|
// Images are stored as files in the App Group container to avoid UserDefaults size limits
|
|
// Used by both AppDelegate and SharedImagePlugin
|
|
//
|
|
|
|
import Foundation
|
|
|
|
public class SharedImageUtility {
|
|
private static let appGroupIdentifier = "group.app.timesafari.share"
|
|
private static let sharedPhotoFileNameKey = "sharedPhotoFileName"
|
|
private static let sharedPhotoFilePathKey = "sharedPhotoFilePath"
|
|
private static let sharedPhotoShareIdKey = "sharedPhotoShareId"
|
|
private static let shareExtensionLastStartKey = "shareExtensionLastStart"
|
|
private static let sharedPhotoReadyKey = "sharedPhotoReady"
|
|
|
|
/// Get the App Group container URL for accessing shared files
|
|
private static var appGroupContainerURL: URL? {
|
|
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
|
|
}
|
|
|
|
private static func logShareDiagnostic(method: String, userDefaults: UserDefaults?) {
|
|
let shareId = userDefaults?.string(forKey: sharedPhotoShareIdKey)
|
|
let filePath = userDefaults?.string(forKey: sharedPhotoFilePathKey)
|
|
let metadataExists = filePath != nil
|
|
let fileExists: Bool
|
|
if let filePath = filePath, let containerURL = appGroupContainerURL {
|
|
let fileURL = containerURL.appendingPathComponent(filePath)
|
|
fileExists = FileManager.default.fileExists(atPath: fileURL.path)
|
|
} else {
|
|
fileExists = false
|
|
}
|
|
|
|
let shareIdLog = shareId ?? "nil"
|
|
let filePathLog = filePath ?? "nil"
|
|
print("[ShareTarget] \(method) shareId=\(shareIdLog) sharedPhotoFilePath=\(filePathLog) metadataExists=\(metadataExists) fileExists=\(fileExists)")
|
|
}
|
|
|
|
/**
|
|
* Get shared image data from App Group container file
|
|
* All images are stored as files for consistency and to avoid UserDefaults size limits
|
|
* Read-only: metadata and file are left intact after retrieval (Phase 1C)
|
|
*
|
|
* @returns Dictionary with "base64" and "fileName" keys, or nil if no shared image
|
|
*/
|
|
static func getSharedImageData() -> [String: String]? {
|
|
let userDefaults = UserDefaults(suiteName: appGroupIdentifier)
|
|
logShareDiagnostic(method: "getSharedImageData", userDefaults: userDefaults)
|
|
|
|
guard let userDefaults = userDefaults else {
|
|
return nil
|
|
}
|
|
|
|
// Get file path and filename from UserDefaults
|
|
guard let filePath = userDefaults.string(forKey: sharedPhotoFilePathKey),
|
|
let containerURL = appGroupContainerURL else {
|
|
return nil
|
|
}
|
|
|
|
let fileName = userDefaults.string(forKey: sharedPhotoFileNameKey) ?? "shared-image.jpg"
|
|
let shareId = userDefaults.string(forKey: sharedPhotoShareIdKey)
|
|
let fileURL = containerURL.appendingPathComponent(filePath)
|
|
|
|
// Read image data from file
|
|
guard let imageData = try? Data(contentsOf: fileURL) else {
|
|
return nil
|
|
}
|
|
|
|
let resolvedShareId = shareId ?? "unknown"
|
|
print("[ShareTarget] shareId=\(resolvedShareId) retrieved")
|
|
print("[ShareTarget] shareId=\(resolvedShareId) left intact after retrieval")
|
|
|
|
// Convert file data to base64 for JavaScript consumption
|
|
let base64String = imageData.base64EncodedString()
|
|
|
|
return ["base64": base64String, "fileName": fileName]
|
|
}
|
|
|
|
/**
|
|
* Check if shared image exists without reading it
|
|
*
|
|
* @returns true if shared image file exists, false otherwise
|
|
*/
|
|
static func hasSharedImage() -> Bool {
|
|
let userDefaults = UserDefaults(suiteName: appGroupIdentifier)
|
|
logShareDiagnostic(method: "hasSharedImage", userDefaults: userDefaults)
|
|
|
|
guard let userDefaults = userDefaults,
|
|
let filePath = userDefaults.string(forKey: sharedPhotoFilePathKey),
|
|
let containerURL = appGroupContainerURL else {
|
|
return false
|
|
}
|
|
|
|
let fileURL = containerURL.appendingPathComponent(filePath)
|
|
return FileManager.default.fileExists(atPath: fileURL.path)
|
|
}
|
|
|
|
/**
|
|
* Diagnostic snapshot of Share Extension startup and pending share state
|
|
* Read-only: does not modify App Group storage
|
|
*/
|
|
static func getShareExtensionDiagnostics() -> [String: Any] {
|
|
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
|
return [
|
|
"shareExtensionLastStart": NSNull(),
|
|
"sharedPhotoShareId": NSNull(),
|
|
"sharedPhotoFilePath": NSNull(),
|
|
"fileExists": false
|
|
]
|
|
}
|
|
|
|
let shareExtensionLastStart = userDefaults.string(forKey: shareExtensionLastStartKey)
|
|
let shareId = userDefaults.string(forKey: sharedPhotoShareIdKey)
|
|
let filePath = userDefaults.string(forKey: sharedPhotoFilePathKey)
|
|
let fileExists: Bool
|
|
if let filePath = filePath, let containerURL = appGroupContainerURL {
|
|
let fileURL = containerURL.appendingPathComponent(filePath)
|
|
fileExists = FileManager.default.fileExists(atPath: fileURL.path)
|
|
} else {
|
|
fileExists = false
|
|
}
|
|
|
|
print("[ShareTarget] getShareExtensionDiagnostics shareExtensionLastStart=\(shareExtensionLastStart ?? "nil") sharedPhotoShareId=\(shareId ?? "nil") sharedPhotoFilePath=\(filePath ?? "nil") fileExists=\(fileExists)")
|
|
|
|
return [
|
|
"shareExtensionLastStart": shareExtensionLastStart ?? NSNull(),
|
|
"sharedPhotoShareId": shareId ?? NSNull(),
|
|
"sharedPhotoFilePath": filePath ?? NSNull(),
|
|
"fileExists": fileExists
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Check if shared photo ready flag is set
|
|
* This flag is set by the Share Extension when image is ready
|
|
*
|
|
* @returns true if flag is set, false otherwise
|
|
*/
|
|
static func isSharedPhotoReady() -> Bool {
|
|
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
|
return false
|
|
}
|
|
|
|
return userDefaults.bool(forKey: sharedPhotoReadyKey)
|
|
}
|
|
|
|
/**
|
|
* Clear the shared photo ready flag
|
|
* Called after processing the shared image
|
|
*/
|
|
static func clearSharedPhotoReadyFlag() {
|
|
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
|
return
|
|
}
|
|
|
|
userDefaults.removeObject(forKey: sharedPhotoReadyKey)
|
|
userDefaults.synchronize()
|
|
}
|
|
}
|
|
|