fix: remove @capacitor/app dynamic import from DeepLinkHandler

Use window.Capacitor.Plugins.App directly instead of dynamic import()
so the build doesn't fail when @capacitor/app isn't in node_modules

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-06-18 22:01:40 +07:00
parent c3ef0247fc
commit be70ff490f

View file

@ -4,54 +4,60 @@ import { useNavigate } from 'react-router-dom';
/** /**
* Forwards OS-level deep-link opens into the React Router navigation stack. * Forwards OS-level deep-link opens into the React Router navigation stack.
* *
* Uses the global window.Capacitor object to avoid a hard npm dependency on
* @capacitor/app (which is installed on the native build machine but not in
* the web/CI build environment).
*
* When the OS launches the app via a URL (Amber NIP-55 callback, custom scheme, * When the OS launches the app via a URL (Amber NIP-55 callback, custom scheme,
* or universal link), Capacitor fires an `appUrlOpen` event. This component * or universal link), Capacitor fires an `appUrlOpen` event. This component
* listens for it and navigates to the correct in-app route. * listens for it and navigates to the correct in-app route.
* *
* Uses HashRouter compatible path extraction — strips the origin/scheme so
* only the pathname+search+hash is passed to navigate().
*
* Must be rendered inside a Router (so `useNavigate` works).
* Safe to render unconditionally — on web it is a no-op.
*
* Amber deep-link example: * Amber deep-link example:
* org.skatespots.app://amber-callback?event=<data> * org.skatespots.app://amber-callback?event=<data>
* → navigates to /amber-callback?event=<data> * → navigates to /amber-callback?event=<data>
*/ */
// Type for the global Capacitor object injected by the native runtime
interface CapacitorGlobal {
isNativePlatform?: () => boolean;
Plugins?: {
App?: {
addListener: (
event: string,
handler: (data: { url: string }) => void
) => Promise<{ remove: () => void }>;
};
};
}
export function DeepLinkHandler() { export function DeepLinkHandler() {
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
// Only run in Capacitor native context const cap = (window as unknown as { Capacitor?: CapacitorGlobal }).Capacitor;
const cap = (window as unknown as { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor;
if (!cap?.isNativePlatform?.()) return; if (!cap?.isNativePlatform?.()) return;
const AppPlugin = cap?.Plugins?.App;
if (!AppPlugin) return;
let removeListener: (() => void) | undefined; let removeListener: (() => void) | undefined;
async function setup() { AppPlugin.addListener('appUrlOpen', (event) => {
try { try {
const { App } = await import('@capacitor/app'); const url = new URL(event.url);
// Extract path + query + fragment from the deep link URL
const listener = await App.addListener('appUrlOpen', (event) => { const path = url.pathname + url.search + url.hash;
try { if (path && path !== '/') {
const url = new URL(event.url); navigate(path, { replace: true });
// Extract path + query + fragment from the deep link URL }
const path = url.pathname + url.search + url.hash;
if (path && path !== '/') {
navigate(path, { replace: true });
}
} catch {
// Invalid URL — ignore
}
});
removeListener = () => listener.remove();
} catch { } catch {
// @capacitor/app not available (web build without Capacitor installed) // Invalid URL — ignore
} }
} }).then((listener) => {
removeListener = () => listener.remove();
setup(); }).catch(() => {
// Plugin not available — ignore
});
return () => { return () => {
removeListener?.(); removeListener?.();