diff --git a/ios/App/App/SharedImagePlugin.swift b/ios/App/App/SharedImagePlugin.swift index 06abe82c..19444207 100644 --- a/ios/App/App/SharedImagePlugin.swift +++ b/ios/App/App/SharedImagePlugin.swift @@ -25,7 +25,11 @@ public class SharedImagePlugin: CAPPlugin, CAPBridgedPlugin { return [ CAPPluginMethod(#selector(getSharedImage(_:)), returnType: .promise), CAPPluginMethod(#selector(hasSharedImage(_:)), returnType: .promise), - CAPPluginMethod(#selector(getShareExtensionDiagnostics(_:)), returnType: .promise) + CAPPluginMethod(#selector(getShareExtensionDiagnostics(_:)), returnType: .promise), + // TEMPORARY SHARE TARGET DIAGNOSTICS + CAPPluginMethod(#selector(getShareExtensionTrace(_:)), returnType: .promise), + // TEMPORARY SHARE TARGET DIAGNOSTICS + CAPPluginMethod(#selector(clearShareExtensionTrace(_:)), returnType: .promise) ] } @@ -70,5 +74,24 @@ public class SharedImagePlugin: CAPPlugin, CAPBridgedPlugin { @objc public func getShareExtensionDiagnostics(_ call: CAPPluginCall) { call.resolve(SharedImageUtility.getShareExtensionDiagnostics()) } + + // TEMPORARY SHARE TARGET DIAGNOSTICS + /** + * Return the raw Share Extension execution trace log from the App Group container + */ + @objc public func getShareExtensionTrace(_ call: CAPPluginCall) { + call.resolve([ + "trace": SharedImageUtility.getShareExtensionTrace() + ]) + } + + // TEMPORARY SHARE TARGET DIAGNOSTICS + /** + * Delete the Share Extension execution trace log if present + */ + @objc public func clearShareExtensionTrace(_ call: CAPPluginCall) { + SharedImageUtility.clearShareExtensionTrace() + call.resolve() + } } diff --git a/ios/App/App/SharedImageUtility.swift b/ios/App/App/SharedImageUtility.swift index 38fd2029..72e2611b 100644 --- a/ios/App/App/SharedImageUtility.swift +++ b/ios/App/App/SharedImageUtility.swift @@ -16,6 +16,8 @@ public class SharedImageUtility { private static let sharedPhotoShareIdKey = "sharedPhotoShareId" private static let shareExtensionLastStartKey = "shareExtensionLastStart" private static let sharedPhotoReadyKey = "sharedPhotoReady" + // TEMPORARY SHARE TARGET DIAGNOSTICS + private static let shareExtensionTraceFileName = "share-extension-trace.log" /// Get the App Group container URL for accessing shared files private static var appGroupContainerURL: URL? { @@ -145,6 +147,33 @@ public class SharedImageUtility { ] } + // TEMPORARY SHARE TARGET DIAGNOSTICS + /** + * Read the Share Extension trace file from the App Group container. + * Read-only: does not modify or delete the trace file. + * + * @returns the full trace contents, or an empty string if no trace exists + */ + static func getShareExtensionTrace() -> String { + guard let containerURL = appGroupContainerURL else { + return "" + } + let fileURL = containerURL.appendingPathComponent(shareExtensionTraceFileName) + return (try? String(contentsOf: fileURL, encoding: .utf8)) ?? "" + } + + // TEMPORARY SHARE TARGET DIAGNOSTICS + /** + * Delete the Share Extension trace file from the App Group container if present. + */ + static func clearShareExtensionTrace() { + guard let containerURL = appGroupContainerURL else { + return + } + let fileURL = containerURL.appendingPathComponent(shareExtensionTraceFileName) + try? FileManager.default.removeItem(at: fileURL) + } + /** * Check if shared photo ready flag is set * This flag is set by the Share Extension when image is ready diff --git a/ios/App/TimeSafariShareExtension/ShareViewController.swift b/ios/App/TimeSafariShareExtension/ShareViewController.swift index a8cdb460..7e599a5d 100644 --- a/ios/App/TimeSafariShareExtension/ShareViewController.swift +++ b/ios/App/TimeSafariShareExtension/ShareViewController.swift @@ -17,12 +17,36 @@ class ShareViewController: UIViewController { private let shareExtensionLastStartKey = "shareExtensionLastStart" private let sharedImageFileName = "shared-image" + // TEMPORARY SHARE TARGET DIAGNOSTICS + private let shareExtensionTraceFileName = "share-extension-trace.log" + /// Get the App Group container URL for storing shared files private var appGroupContainerURL: URL? { return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) } + // TEMPORARY SHARE TARGET DIAGNOSTICS + /// Append a single timestamped line to the Share Extension trace file in the + /// App Group container. Each line is prefixed with an ISO8601 timestamp. + /// Logging failures are intentionally ignored (diagnostics only). + private func appendTrace(_ message: String) { + guard let containerURL = appGroupContainerURL else { return } + let fileURL = containerURL.appendingPathComponent(shareExtensionTraceFileName) + let timestamp = ISO8601DateFormatter().string(from: Date()) + let line = "\(timestamp) \(message)\n" + guard let data = line.data(using: .utf8) else { return } + if let handle = try? FileHandle(forWritingTo: fileURL) { + defer { try? handle.close() } + _ = try? handle.seekToEnd() + try? handle.write(contentsOf: data) + } else { + try? data.write(to: fileURL, options: .atomic) + } + } + override func viewDidLoad() { + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("viewDidLoad START") if let userDefaults = UserDefaults(suiteName: appGroupIdentifier) { let timestamp = ISO8601DateFormatter().string(from: Date()) userDefaults.set(timestamp, forKey: shareExtensionLastStartKey) @@ -39,19 +63,29 @@ class ShareViewController: UIViewController { // Process image immediately without showing UI processAndOpenApp() print("[ShareTarget] viewDidLoad completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("viewDidLoad END") } private func processAndOpenApp() { print("[ShareTarget] processAndOpenApp started") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("processAndOpenApp START") // extensionContext is automatically available on UIViewController when used as extension principal class guard let context = extensionContext, let inputItems = context.inputItems as? [NSExtensionItem] else { print("[ShareTarget] processAndOpenApp failed: missing extensionContext or inputItems") print("[ShareTarget] completeRequest starting") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("completeRequest START") extensionContext?.completeRequest(returningItems: [], completionHandler: nil) print("[ShareTarget] completeRequest completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("completeRequest END") print("[ShareTarget] processAndOpenApp completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("processAndOpenApp END") return } @@ -64,6 +98,8 @@ class ShareViewController: UIViewController { guard let self = self, let context = self.extensionContext else { print("[ShareTarget] processAndOpenApp failed: self or extensionContext unavailable in completion") print("[ShareTarget] processAndOpenApp completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self?.appendTrace("processAndOpenApp END") return } @@ -78,23 +114,35 @@ class ShareViewController: UIViewController { // Complete immediately - no UI shown print("[ShareTarget] completeRequest starting") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self.appendTrace("completeRequest START") context.completeRequest(returningItems: [], completionHandler: nil) print("[ShareTarget] completeRequest completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self.appendTrace("completeRequest END") print("[ShareTarget] processAndOpenApp completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self.appendTrace("processAndOpenApp END") } } private func setSharedPhotoReadyFlag() { print("[ShareTarget] setSharedPhotoReadyFlag started") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("setSharedPhotoReadyFlag START") guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else { print("[ShareTarget] setSharedPhotoReadyFlag failed: UserDefaults unavailable for app group") print("[ShareTarget] setSharedPhotoReadyFlag completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("setSharedPhotoReadyFlag FAILURE") return } userDefaults.set(true, forKey: "sharedPhotoReady") userDefaults.synchronize() print("[ShareTarget] setSharedPhotoReadyFlag success") print("[ShareTarget] setSharedPhotoReadyFlag completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("setSharedPhotoReadyFlag SUCCESS") } private func processSharedImage(from items: [NSExtensionItem], completion: @escaping (Bool) -> Void) { @@ -102,6 +150,8 @@ class ShareViewController: UIViewController { count + (item.attachments?.count ?? 0) } print("[ShareTarget] processSharedImage started attachmentCount=\(attachmentCount)") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("processSharedImage START") // Find the first image attachment for item in items { @@ -120,6 +170,8 @@ class ShareViewController: UIViewController { let shareId = UUID().uuidString print("[ShareTarget] processSharedImage found image attachment shareId=\(shareId) UTType=\(UTType.image.identifier)") print("[ShareTarget] share received shareId=\(shareId)") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("image attachment found shareId=\(shareId)") // Try to load raw data first to preserve original format // This preserves the original image format without conversion @@ -134,6 +186,8 @@ class ShareViewController: UIViewController { if let error = error { print("[ShareTarget] processSharedImage failed: loadItem error shareId=\(shareId) error=\(error.localizedDescription)") print("[ShareTarget] processSharedImage completed shareId=\(shareId) success=false") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self.appendTrace("processSharedImage END success=false") completion(false) return } @@ -177,19 +231,27 @@ class ShareViewController: UIViewController { guard let finalImageData = imageData else { print("[ShareTarget] processSharedImage failed: no image data shareId=\(shareId)") print("[ShareTarget] processSharedImage completed shareId=\(shareId) success=false") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self.appendTrace("processSharedImage END success=false") completion(false) return } print("[ShareTarget] image loaded bytes=\(finalImageData.count) filename=\(fileName) shareId=\(shareId)") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self.appendTrace("image loaded shareId=\(shareId) bytes=\(finalImageData.count)") // Store image as file in App Group container if self.storeImageData(finalImageData, fileName: fileName, shareId: shareId) { print("[ShareTarget] processSharedImage completed shareId=\(shareId) success=true") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self.appendTrace("processSharedImage END success=true") completion(true) } else { print("[ShareTarget] processSharedImage failed: storeImageData returned false shareId=\(shareId)") print("[ShareTarget] processSharedImage completed shareId=\(shareId) success=false") + // TEMPORARY SHARE TARGET DIAGNOSTICS + self.appendTrace("processSharedImage END success=false") completion(false) } } @@ -200,6 +262,8 @@ class ShareViewController: UIViewController { // No image found print("[ShareTarget] processSharedImage failed: no image attachment found attachmentCount=\(attachmentCount)") print("[ShareTarget] processSharedImage completed success=false") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("processSharedImage END success=false") completion(false) } @@ -227,10 +291,14 @@ class ShareViewController: UIViewController { /// Returns true if successful, false otherwise private func storeImageData(_ imageData: Data, fileName: String, shareId: String) -> Bool { print("[ShareTarget] storeImageData started shareId=\(shareId) bytes=\(imageData.count) filename=\(fileName)") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("storeImageData START shareId=\(shareId)") guard let containerURL = appGroupContainerURL else { print("[ShareTarget] storeImageData failed: app group container unavailable shareId=\(shareId)") print("[ShareTarget] storeImageData completed shareId=\(shareId) success=false") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("storeImageData FAILURE shareId=\(shareId)") return false } @@ -253,6 +321,8 @@ class ShareViewController: UIViewController { } catch { print("[ShareTarget] storeImageData failed: file write error shareId=\(shareId) error=\(error.localizedDescription)") print("[ShareTarget] storeImageData completed shareId=\(shareId) success=false") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("storeImageData FAILURE shareId=\(shareId)") return false } print("[ShareTarget] file stored shareId=\(shareId) originalFilename=\(originalFileName) storedFilename=\(storedFileName)") @@ -261,6 +331,8 @@ class ShareViewController: UIViewController { guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else { print("[ShareTarget] storeImageData failed: UserDefaults unavailable shareId=\(shareId)") print("[ShareTarget] storeImageData completed shareId=\(shareId) success=false") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("storeImageData FAILURE shareId=\(shareId)") return false } @@ -276,16 +348,22 @@ class ShareViewController: UIViewController { print("[ShareTarget] metadata stored shareId=\(shareId) originalFilename=\(originalFileName) storedFilename=\(storedFileName)") print("[ShareTarget] storeImageData success shareId=\(shareId)") print("[ShareTarget] storeImageData completed shareId=\(shareId) success=true") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("storeImageData SUCCESS shareId=\(shareId)") return true } private func openMainApp() { print("[ShareTarget] openMainApp starting") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("openMainApp START") // Open the main app with minimal URL - app will detect shared data on activation guard let url = URL(string: "timesafari://") else { print("[ShareTarget] openMainApp failed: could not create timesafari:// URL") print("[ShareTarget] openMainApp completed") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("openMainApp FAILURE") return } @@ -294,6 +372,8 @@ class ShareViewController: UIViewController { if let application = responder as? UIApplication { application.open(url, options: [:], completionHandler: nil) print("[ShareTarget] openMainApp completed via UIApplication") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("openMainApp SUCCESS") return } responder = responder?.next @@ -302,6 +382,8 @@ class ShareViewController: UIViewController { // Fallback: use extension context extensionContext?.open(url, completionHandler: nil) print("[ShareTarget] openMainApp completed via extensionContext fallback") + // TEMPORARY SHARE TARGET DIAGNOSTICS + appendTrace("openMainApp SUCCESS") } } diff --git a/src/plugins/SharedImagePlugin.web.ts b/src/plugins/SharedImagePlugin.web.ts index 70f8b35e..7536f66a 100644 --- a/src/plugins/SharedImagePlugin.web.ts +++ b/src/plugins/SharedImagePlugin.web.ts @@ -8,6 +8,8 @@ import type { SharedImagePlugin, SharedImageResult, ShareExtensionDiagnostics, + // TEMPORARY SHARE TARGET DIAGNOSTICS + ShareExtensionTrace, } from "./definitions"; export class SharedImagePluginWeb @@ -33,4 +35,14 @@ export class SharedImagePluginWeb pendingShareExists: false, }; } + + // TEMPORARY SHARE TARGET DIAGNOSTICS + async getShareExtensionTrace(): Promise { + return { trace: "" }; + } + + // TEMPORARY SHARE TARGET DIAGNOSTICS + async clearShareExtensionTrace(): Promise { + // Web platform doesn't support native sharing - no-op + } } diff --git a/src/plugins/definitions.ts b/src/plugins/definitions.ts index 5095b065..c240e834 100644 --- a/src/plugins/definitions.ts +++ b/src/plugins/definitions.ts @@ -16,6 +16,11 @@ export interface ShareExtensionDiagnostics { pendingShareExists: boolean; } +// TEMPORARY SHARE TARGET DIAGNOSTICS +export interface ShareExtensionTrace { + trace: string; +} + export interface SharedImagePlugin { /** * Get shared image data from native layer @@ -34,4 +39,16 @@ export interface SharedImagePlugin { * Diagnostic snapshot of Share Extension startup and pending share state (iOS) */ getShareExtensionDiagnostics(): Promise; + + // TEMPORARY SHARE TARGET DIAGNOSTICS + /** + * Read the raw Share Extension execution trace log (iOS) + */ + getShareExtensionTrace(): Promise; + + // TEMPORARY SHARE TARGET DIAGNOSTICS + /** + * Delete the Share Extension execution trace log if present (iOS) + */ + clearShareExtensionTrace(): Promise; }