forked from trent_larson/crowd-funder-for-time-pwa
feat(mobile): add deep linking support for Capacitor apps
Adds native deep linking capabilities: - Configure timesafari:// URL scheme for iOS and Android - Add @capacitor/app dependency and configuration - Implement deep link handler with improved error logging - Support parameterized routes like claim/:id - Add debug logging for native platforms - Handle app mounting state for deep links Technical changes: - Update AndroidManifest.xml with intent filters - Add URL scheme to iOS Info.plist - Add @capacitor/app to Podfile and Gradle - Enhance main.capacitor.ts with robust deep link handling
This commit is contained in:
@@ -1,16 +1,79 @@
|
||||
import { initializeApp } from "./main.common";
|
||||
import { App } from "@capacitor/app";
|
||||
import router from "./router";
|
||||
import { handleApiError } from "./services/api";
|
||||
import { loadPlanWithRetry } from "./services/plan";
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
|
||||
const app = initializeApp();
|
||||
|
||||
// Handle deep links
|
||||
App.addListener("appUrlOpen", (data: { url: string }) => {
|
||||
console.log("Deep link opened:", data.url);
|
||||
const slug = data.url.replace("timesafari://", "");
|
||||
if (slug) {
|
||||
router.push("/" + slug);
|
||||
// Store initial deep link if app is not ready
|
||||
let pendingDeepLink: string | null = null;
|
||||
|
||||
// Initialize API error handling
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
if (event.reason?.response) {
|
||||
handleApiError(event.reason, event.reason.config?.url || 'unknown');
|
||||
}
|
||||
});
|
||||
|
||||
// Create reusable handler function
|
||||
const handleDeepLink = async (data: { url: string }) => {
|
||||
try {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
console.log("[Capacitor Deep Link] START Handler");
|
||||
console.log("[Capacitor Deep Link] Received URL:", data.url);
|
||||
}
|
||||
|
||||
// Wait for app to be mounted
|
||||
if (!app._container) {
|
||||
console.log("[Capacitor Deep Link] Waiting for app mount");
|
||||
await new Promise<void>((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (app._container) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
const url = new URL(data.url);
|
||||
const path = url.pathname.substring(1);
|
||||
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
console.log("[Capacitor Deep Link] Parsed path:", path);
|
||||
}
|
||||
|
||||
// Map parameterized routes
|
||||
const paramRoutes = {
|
||||
'claim': /^claim\/(.+)$/,
|
||||
};
|
||||
|
||||
// Check if path matches any parameterized route
|
||||
for (const [routeName, pattern] of Object.entries(paramRoutes)) {
|
||||
const match = path.match(pattern);
|
||||
if (match) {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
console.log(`[Capacitor Deep Link] Matched route: ${routeName}, param: ${match[1]}`);
|
||||
}
|
||||
await router.push({
|
||||
name: routeName,
|
||||
params: { id: match[1] }
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await router.push('/' + path);
|
||||
} catch (error) {
|
||||
console.error("[Capacitor Deep Link] Error:", error);
|
||||
handleApiError(error, 'deep-link');
|
||||
}
|
||||
};
|
||||
|
||||
// Register listener
|
||||
App.addListener("appUrlOpen", handleDeepLink);
|
||||
|
||||
// Mount app
|
||||
app.mount("#app");
|
||||
|
||||
13
src/services/api.ts
Normal file
13
src/services/api.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export const handleApiError = (error: any, endpoint: string) => {
|
||||
if (process.env.VITE_PLATFORM === 'capacitor') {
|
||||
console.error(`[Capacitor API Error] ${endpoint}:`, error);
|
||||
}
|
||||
|
||||
// Specific handling for rate limits
|
||||
if (error.response?.status === 400) {
|
||||
console.warn(`[Rate Limit] ${endpoint}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
19
src/services/plan.ts
Normal file
19
src/services/plan.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export const loadPlanWithRetry = async (handle: string, retries = 3) => {
|
||||
for (let i = 0; i < retries; i++) {
|
||||
try {
|
||||
const plan = await loadPlan(handle);
|
||||
if (plan) return plan;
|
||||
} catch (err) {
|
||||
console.warn(`[Plan Load] Attempt ${i + 1} failed for ${handle}`);
|
||||
if (i === retries - 1) throw err;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadPlan = async (handle: string) => {
|
||||
// Implement your plan loading logic here
|
||||
// This is a placeholder - replace with actual implementation
|
||||
const response = await fetch(`/api/plans/${handle}`);
|
||||
return response.json();
|
||||
};
|
||||
Reference in New Issue
Block a user