feat(ios): clean up native share storage after import (Phase 1F)

Add SharedImageUtility.clearProcessedShare() to permanently delete the
shared image file and remove all App Group metadata (sharedPhotoFileName,
sharedPhotoFilePath, sharedPhotoShareId, sharedPhotoReady,
sharedPhotoProcessedShareId). The helper is idempotent and logs cleanup
started/completed plus a warning on file-deletion failure. Expose it through
the SharedImage plugin with a web no-op stub.

Wire cleanup into the import flow so it runs only after both
storeSharedImageInTempDB() and markProcessed() succeed; a cleanup failure is
logged but does not fail the already-successful import. After cleanup a
subsequent getSharedImage() finds no pending share. iOS only; Android, the
Share Extension, and Phase 1E duplicate suppression are unchanged.
This commit is contained in:
Jose Olarte III
2026-06-30 22:17:37 +08:00
parent 7093f38e09
commit 0c91b687f9
5 changed files with 96 additions and 1 deletions

View File

@@ -25,7 +25,8 @@ public class SharedImagePlugin: CAPPlugin, CAPBridgedPlugin {
return [
CAPPluginMethod(#selector(getSharedImage(_:)), returnType: .promise),
CAPPluginMethod(#selector(hasSharedImage(_:)), returnType: .promise),
CAPPluginMethod(#selector(markProcessed(_:)), returnType: .promise)
CAPPluginMethod(#selector(markProcessed(_:)), returnType: .promise),
CAPPluginMethod(#selector(clearProcessedShare(_:)), returnType: .promise)
]
}
@@ -82,5 +83,17 @@ public class SharedImagePlugin: CAPPlugin, CAPBridgedPlugin {
"success": success
])
}
/**
* Permanently remove the currently processed share from App Group storage (Phase 1F)
* Deletes the image file and all share metadata. Idempotent.
* Intended to be called only after successful processing and markProcessed().
*/
@objc public func clearProcessedShare(_ call: CAPPluginCall) {
let success = SharedImageUtility.clearProcessedShare()
call.resolve([
"success": success
])
}
}

View File

@@ -117,6 +117,55 @@ public class SharedImageUtility {
print("[ShareTarget] shareId=\(shareId) marked processed")
return true
}
/**
* Permanently remove the currently processed share from App Group storage (Phase 1F).
*
* Deletes the shared image file (if present) and removes all share metadata
* keys. Idempotent: missing files or keys are treated as success. Intended
* to be called only after the share has been successfully handed off to the
* app and marked processed.
*
* @returns true if cleanup completed without error, false if file deletion failed
*/
@discardableResult
static func clearProcessedShare() -> Bool {
let userDefaults = UserDefaults(suiteName: appGroupIdentifier)
let shareId = userDefaults?.string(forKey: sharedPhotoShareIdKey)
?? userDefaults?.string(forKey: sharedPhotoProcessedShareIdKey)
?? "unknown"
print("[ShareTarget] shareId=\(shareId) cleanup started")
var success = true
// Delete the shared image file if it exists
if let filePath = userDefaults?.string(forKey: sharedPhotoFilePathKey),
let containerURL = appGroupContainerURL {
let fileURL = containerURL.appendingPathComponent(filePath)
if FileManager.default.fileExists(atPath: fileURL.path) {
do {
try FileManager.default.removeItem(at: fileURL)
} catch {
success = false
print("[ShareTarget] shareId=\(shareId) cleanup warning: failed to delete file error=\(error.localizedDescription)")
}
}
}
// Remove all share metadata keys (removeObject is a no-op when absent)
if let userDefaults = userDefaults {
userDefaults.removeObject(forKey: sharedPhotoFileNameKey)
userDefaults.removeObject(forKey: sharedPhotoFilePathKey)
userDefaults.removeObject(forKey: sharedPhotoShareIdKey)
userDefaults.removeObject(forKey: sharedPhotoReadyKey)
userDefaults.removeObject(forKey: sharedPhotoProcessedShareIdKey)
userDefaults.synchronize()
}
print("[ShareTarget] shareId=\(shareId) cleanup completed")
return success
}
/**
* Check if shared image exists without reading it