WIP: iOS Share Target Reliability #235

Draft
jose wants to merge 31 commits from fix/ios-share-target-reliability into master
Showing only changes of commit 02e6e3427d - Show all commits

View File

@@ -471,71 +471,6 @@ logger.log("[Capacitor] 🚀 Mounting app");
app.mount("#app");
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)
// 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
@@ -550,15 +485,11 @@ if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "android") {
}, delay);
});
} else if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "ios") {
// Phase 2A: replace the fixed 1000ms delay with a deterministic wait for the
// SharedImage plugin. The very first startup check runs only after the plugin
// is confirmed available; if the bounded retry budget is exhausted we still
// attempt once as a best-effort fallback (appStateChange retries on the next
// activation), so startup is never worse than the previous fixed-delay path.
void (async () => {
await waitForSharedImagePluginReady();
await checkForSharedImageAndNavigate();
})();
// Phase 2B-3: the SharedImage plugin is now registered deterministically from
// AppBridgeViewController.capacitorDidLoad() before the web layer loads, so it
// is guaranteed to exist here. Perform the initial shared-image check
// immediately without waiting/polling for plugin readiness.
void checkForSharedImageAndNavigate();
}
// Listen for app state changes to detect when app becomes active