feat(ios): use UUID-based filenames for shared images (Phase 1B)

Store shared images as <shareId>.<ext> in the App Group container while
keeping the original filename in metadata, preventing on-disk collisions
without changing retrieval, deletion, or JS consumer behavior.
This commit is contained in:
Jose Olarte III
2026-06-23 19:37:11 +08:00
parent 35a6a6bfb3
commit ddbd07f315
3 changed files with 80 additions and 21 deletions

View File

@@ -48,9 +48,7 @@ public class SharedImageUtility {
return nil
}
if let shareId = shareId {
print("[ShareTarget] share retrieved shareId=\(shareId)")
}
print("[ShareTarget] share retrieved shareId=\(shareId ?? "unknown") originalFilename=\(fileName) storedFilename=\(filePath)")
// Convert file data to base64 for JavaScript consumption
let base64String = imageData.base64EncodedString()

View File

@@ -149,7 +149,18 @@ class ShareViewController: UIViewController {
}
return "shared-image.\(newExtension)"
}
/// Extract file extension from original filename, defaulting to jpg when absent
private func fileExtension(from fileName: String) -> String {
let ext = (fileName as NSString).pathExtension
return ext.isEmpty ? "jpg" : ext.lowercased()
}
/// Build unique on-disk filename: <shareId>.<extension>
private func storedFileName(shareId: String, originalFileName: String) -> String {
return "\(shareId).\(fileExtension(from: originalFileName))"
}
/// Store image data as a file in the App Group container
/// All images are stored as files regardless of size for consistency and simplicity
/// Returns true if successful, false otherwise
@@ -157,38 +168,43 @@ class ShareViewController: UIViewController {
guard let containerURL = appGroupContainerURL else {
return false
}
// Create file URL in the container using the actual filename
// Extract extension from fileName if present, otherwise use sharedImageFileName
let actualFileName = fileName.isEmpty ? sharedImageFileName : fileName
let fileURL = containerURL.appendingPathComponent(actualFileName)
// Remove old file if it exists
try? FileManager.default.removeItem(at: fileURL)
let originalFileName = fileName.isEmpty ? "\(sharedImageFileName).jpg" : fileName
let storedFileName = storedFileName(shareId: shareId, originalFileName: originalFileName)
let fileURL = containerURL.appendingPathComponent(storedFileName)
// Remove previously pending share file (metadata tracks one share at a time)
if let userDefaults = UserDefaults(suiteName: appGroupIdentifier),
let previousPath = userDefaults.string(forKey: sharedPhotoFilePathKey) {
let previousURL = containerURL.appendingPathComponent(previousPath)
if previousURL != fileURL {
try? FileManager.default.removeItem(at: previousURL)
}
}
// Write image data to file
do {
try imageData.write(to: fileURL)
} catch {
return false
}
print("[ShareTarget] file stored shareId=\(shareId)")
print("[ShareTarget] file stored shareId=\(shareId) originalFilename=\(originalFileName) storedFilename=\(storedFileName)")
// Store file path and filename in UserDefaults (small data, safe to store)
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
return false
}
// Store relative path, filename, and share identifier
userDefaults.set(actualFileName, forKey: sharedPhotoFilePathKey)
userDefaults.set(fileName, forKey: sharedPhotoFileNameKey)
// sharedPhotoFilePath = on-disk name; sharedPhotoFileName = original display name
userDefaults.set(storedFileName, forKey: sharedPhotoFilePathKey)
userDefaults.set(originalFileName, forKey: sharedPhotoFileNameKey)
userDefaults.set(shareId, forKey: sharedPhotoShareIdKey)
// Clean up any old base64 data that might exist
userDefaults.removeObject(forKey: "sharedPhotoBase64")
userDefaults.synchronize()
print("[ShareTarget] metadata stored shareId=\(shareId)")
print("[ShareTarget] metadata stored shareId=\(shareId) originalFilename=\(originalFileName) storedFilename=\(storedFileName)")
return true
}