Compare commits
3 Commits
9941264022
...
02e6e3427d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02e6e3427d | ||
|
|
337a8f7536 | ||
|
|
4978e93711 |
@@ -18,6 +18,7 @@
|
|||||||
C86585DF2ED456DE00824752 /* TimeSafariShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C86585D52ED456DE00824752 /* TimeSafariShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
C86585DF2ED456DE00824752 /* TimeSafariShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C86585D52ED456DE00824752 /* TimeSafariShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
C8C56E142EE0474B00737D0E /* SharedImageUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C56E132EE0474B00737D0E /* SharedImageUtility.swift */; };
|
C8C56E142EE0474B00737D0E /* SharedImageUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C56E132EE0474B00737D0E /* SharedImageUtility.swift */; };
|
||||||
C8C56E162EE064CB00737D0E /* SharedImagePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C56E152EE064CA00737D0E /* SharedImagePlugin.swift */; };
|
C8C56E162EE064CB00737D0E /* SharedImagePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C56E152EE064CA00737D0E /* SharedImagePlugin.swift */; };
|
||||||
|
C8C56E182EE0700A00737D0E /* AppBridgeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C56E172EE0700A00737D0E /* AppBridgeViewController.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
C86585E52ED4577F00824752 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
|
C86585E52ED4577F00824752 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
|
||||||
C8C56E132EE0474B00737D0E /* SharedImageUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedImageUtility.swift; sourceTree = "<group>"; };
|
C8C56E132EE0474B00737D0E /* SharedImageUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedImageUtility.swift; sourceTree = "<group>"; };
|
||||||
C8C56E152EE064CA00737D0E /* SharedImagePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedImagePlugin.swift; sourceTree = "<group>"; };
|
C8C56E152EE064CA00737D0E /* SharedImagePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedImagePlugin.swift; sourceTree = "<group>"; };
|
||||||
|
C8C56E172EE0700A00737D0E /* AppBridgeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBridgeViewController.swift; sourceTree = "<group>"; };
|
||||||
E2E9297D5D02C549106C77F9 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
|
E2E9297D5D02C549106C77F9 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
EAEC6436E595F7CD3A1C9E96 /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
EAEC6436E595F7CD3A1C9E96 /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -127,6 +129,7 @@
|
|||||||
504EC3061FED79650016851F /* App */ = {
|
504EC3061FED79650016851F /* App */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
C8C56E172EE0700A00737D0E /* AppBridgeViewController.swift */,
|
||||||
C8C56E152EE064CA00737D0E /* SharedImagePlugin.swift */,
|
C8C56E152EE064CA00737D0E /* SharedImagePlugin.swift */,
|
||||||
C8C56E132EE0474B00737D0E /* SharedImageUtility.swift */,
|
C8C56E132EE0474B00737D0E /* SharedImageUtility.swift */,
|
||||||
C86585E52ED4577F00824752 /* App.entitlements */,
|
C86585E52ED4577F00824752 /* App.entitlements */,
|
||||||
@@ -346,6 +349,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
C8C56E162EE064CB00737D0E /* SharedImagePlugin.swift in Sources */,
|
C8C56E162EE064CB00737D0E /* SharedImagePlugin.swift in Sources */,
|
||||||
|
C8C56E182EE0700A00737D0E /* AppBridgeViewController.swift in Sources */,
|
||||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||||
C8C56E142EE0474B00737D0E /* SharedImageUtility.swift in Sources */,
|
C8C56E142EE0474B00737D0E /* SharedImageUtility.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|||||||
30
ios/App/App/AppBridgeViewController.swift
Normal file
30
ios/App/App/AppBridgeViewController.swift
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// AppBridgeViewController.swift
|
||||||
|
// App
|
||||||
|
//
|
||||||
|
// Capacitor bridge view controller subclass.
|
||||||
|
//
|
||||||
|
// Phase 2B-1: registers the app-local SharedImagePlugin from the deterministic
|
||||||
|
// capacitorDidLoad() lifecycle callback, where the Capacitor bridge is
|
||||||
|
// guaranteed to exist. The existing AppDelegate registration is intentionally
|
||||||
|
// left in place as a temporary safety net during this phase.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Capacitor
|
||||||
|
|
||||||
|
class AppBridgeViewController: CAPBridgeViewController {
|
||||||
|
|
||||||
|
override func capacitorDidLoad() {
|
||||||
|
super.capacitorDidLoad()
|
||||||
|
|
||||||
|
// Register the app-local SharedImage plugin using the same approach as
|
||||||
|
// AppDelegate. The @objc(SharedImage) annotation exposes it as
|
||||||
|
// "SharedImage" to JavaScript. At this point the bridge is guaranteed
|
||||||
|
// to be available (capacitorDidLoad runs immediately after the bridge
|
||||||
|
// is created).
|
||||||
|
let pluginInstance = SharedImagePlugin()
|
||||||
|
bridge?.registerPluginInstance(pluginInstance)
|
||||||
|
print("[AppBridgeViewController] ✅ Registered SharedImagePlugin (exposed as 'SharedImage' via @objc annotation)")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,50 +22,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||||||
// Initialize SQLite
|
// Initialize SQLite
|
||||||
//let sqlite = SQLite()
|
//let sqlite = SQLite()
|
||||||
//sqlite.initialize()
|
//sqlite.initialize()
|
||||||
|
|
||||||
// Register SharedImage plugin manually after bridge is ready
|
// SharedImagePlugin is registered from AppBridgeViewController.capacitorDidLoad()
|
||||||
// Try multiple times with increasing delays to ensure bridge is initialized
|
// (Phase 2B-2). The previous AppDelegate retry-based registration was removed.
|
||||||
var attempts = 0
|
|
||||||
let maxAttempts = 5
|
|
||||||
|
|
||||||
func tryRegister() {
|
|
||||||
attempts += 1
|
|
||||||
if registerSharedImagePlugin() {
|
|
||||||
print("[AppDelegate] ✅ Plugin registration successful on attempt \(attempts)")
|
|
||||||
} else if attempts < maxAttempts {
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + Double(attempts) * 0.5) {
|
|
||||||
tryRegister()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("[AppDelegate] ⚠️ Failed to register plugin after \(maxAttempts) attempts")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start registration attempts
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
||||||
tryRegister()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
private func registerSharedImagePlugin() -> Bool {
|
|
||||||
guard let window = self.window,
|
|
||||||
let bridgeVC = window.rootViewController as? CAPBridgeViewController,
|
|
||||||
let bridge = bridgeVC.bridge else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create plugin instance
|
|
||||||
// The @objc(SharedImage) annotation makes it available as "SharedImage" to Objective-C
|
|
||||||
// which matches the JavaScript registration name
|
|
||||||
let pluginInstance = SharedImagePlugin()
|
|
||||||
bridge.registerPluginInstance(pluginInstance)
|
|
||||||
print("[AppDelegate] ✅ Registered SharedImagePlugin (exposed as 'SharedImage' via @objc annotation)")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillResignActive(_ application: UIApplication) {
|
func applicationWillResignActive(_ application: UIApplication) {
|
||||||
// TEMPORARY SHARE TARGET DIAGNOSTICS
|
// TEMPORARY SHARE TARGET DIAGNOSTICS
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<!--Bridge View Controller-->
|
<!--Bridge View Controller-->
|
||||||
<scene sceneID="tne-QT-ifu">
|
<scene sceneID="tne-QT-ifu">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
|
<viewController id="BYZ-38-t0r" customClass="AppBridgeViewController" customModule="App" customModuleProvider="target" sceneMemberID="viewController"/>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
</scene>
|
</scene>
|
||||||
|
|||||||
@@ -471,71 +471,6 @@ logger.log("[Capacitor] 🚀 Mounting app");
|
|||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
logger.info(`[Main] ✅ App mounted successfully`);
|
logger.info(`[Main] ✅ App mounted successfully`);
|
||||||
|
|
||||||
/**
|
|
||||||
* Phase 2A: deterministic startup readiness for the SharedImage plugin (iOS).
|
|
||||||
*
|
|
||||||
* The native SharedImage plugin is registered asynchronously by AppDelegate
|
|
||||||
* (with its own retry budget), so a fixed startup delay cannot guarantee the
|
|
||||||
* plugin is reachable from JS before the first shared-image check. Instead of
|
|
||||||
* assuming readiness after an arbitrary sleep, we probe the plugin with a
|
|
||||||
* lightweight, read-only hasSharedImage() call and retry for a short bounded
|
|
||||||
* period until it responds or the budget is exhausted.
|
|
||||||
*
|
|
||||||
* This applies ONLY to the very first startup shared-image check. The
|
|
||||||
* appStateChange and appUrlOpen paths are intentionally left unchanged.
|
|
||||||
*/
|
|
||||||
const STARTUP_PLUGIN_MAX_ATTEMPTS = 10;
|
|
||||||
const STARTUP_PLUGIN_RETRY_DELAY_MS = 300;
|
|
||||||
|
|
||||||
function sleep(ms: number): Promise<void> {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait until the iOS SharedImage native plugin is available.
|
|
||||||
*
|
|
||||||
* Probes the plugin with a read-only hasSharedImage() call; a successful
|
|
||||||
* resolution means the native plugin instance is registered and reachable.
|
|
||||||
* Retries up to STARTUP_PLUGIN_MAX_ATTEMPTS with STARTUP_PLUGIN_RETRY_DELAY_MS
|
|
||||||
* between attempts.
|
|
||||||
*
|
|
||||||
* @returns true once the plugin responds, false if the retry budget is exhausted
|
|
||||||
*/
|
|
||||||
async function waitForSharedImagePluginReady(): Promise<boolean> {
|
|
||||||
// TEMPORARY SHARE TARGET DIAGNOSTICS
|
|
||||||
console.info(
|
|
||||||
"[ShareTarget] Startup shared-image check waiting for SharedImage plugin",
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= STARTUP_PLUGIN_MAX_ATTEMPTS; attempt++) {
|
|
||||||
try {
|
|
||||||
// Lightweight, read-only probe. hasSharedImage() does not consume or
|
|
||||||
// mutate the pending share, so probing is side-effect free.
|
|
||||||
await SharedImage.hasSharedImage();
|
|
||||||
// TEMPORARY SHARE TARGET DIAGNOSTICS
|
|
||||||
console.info(
|
|
||||||
`[ShareTarget] SharedImage plugin ready after ${attempt} attempt(s)`,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
// Plugin not registered yet; wait and retry within the bounded budget.
|
|
||||||
logger.debug(
|
|
||||||
`[Main] SharedImage plugin not ready (attempt ${attempt}/${STARTUP_PLUGIN_MAX_ATTEMPTS}):`,
|
|
||||||
error instanceof Error ? error.message : String(error),
|
|
||||||
);
|
|
||||||
if (attempt < STARTUP_PLUGIN_MAX_ATTEMPTS) {
|
|
||||||
await sleep(STARTUP_PLUGIN_RETRY_DELAY_MS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TEMPORARY SHARE TARGET DIAGNOSTICS
|
|
||||||
console.info(
|
|
||||||
`[ShareTarget] Startup shared-image check giving up after ${STARTUP_PLUGIN_MAX_ATTEMPTS} attempt(s)`,
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for shared image on initial load (in case app was launched from share sheet)
|
// Check for shared image on initial load (in case app was launched from share sheet)
|
||||||
// On Android, share intents are processed in MainActivity.onCreate, so we need to check
|
// On Android, share intents are processed in MainActivity.onCreate, so we need to check
|
||||||
// after a delay to ensure the native code has finished processing
|
// after a delay to ensure the native code has finished processing
|
||||||
@@ -550,15 +485,11 @@ if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "android") {
|
|||||||
}, delay);
|
}, delay);
|
||||||
});
|
});
|
||||||
} else if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "ios") {
|
} else if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "ios") {
|
||||||
// Phase 2A: replace the fixed 1000ms delay with a deterministic wait for the
|
// Phase 2B-3: the SharedImage plugin is now registered deterministically from
|
||||||
// SharedImage plugin. The very first startup check runs only after the plugin
|
// AppBridgeViewController.capacitorDidLoad() before the web layer loads, so it
|
||||||
// is confirmed available; if the bounded retry budget is exhausted we still
|
// is guaranteed to exist here. Perform the initial shared-image check
|
||||||
// attempt once as a best-effort fallback (appStateChange retries on the next
|
// immediately without waiting/polling for plugin readiness.
|
||||||
// activation), so startup is never worse than the previous fixed-delay path.
|
void checkForSharedImageAndNavigate();
|
||||||
void (async () => {
|
|
||||||
await waitForSharedImagePluginReady();
|
|
||||||
await checkForSharedImageAndNavigate();
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for app state changes to detect when app becomes active
|
// Listen for app state changes to detect when app becomes active
|
||||||
|
|||||||
Reference in New Issue
Block a user