Web to Native Communication Flow
This document explains how Bagisto Native uses an event-driven bridge (Hotwire + Stimulus + native bridge) to enable safe, decoupled two-way communication between a web app and a native mobile app.
It includes diagrams, code examples, event names, and a quick integration checklist.
Architecture (high level) 🏗️
Mermaid diagram (copyable; render with a mermaid-supported viewer):
Fallback ASCII diagram:
Web App (React/Next) <---- Hotwire Bridge ----> Native App (iOS / Android)
[Hidden <hotwire-*>] -- data-* attrs / clicks --> [BridgeComponent.send(...)]
[window events] <-- CustomEvent responses / callbacks -- [native sends events]Components Involved 🔧
- @bagisto-native/core
- Web Components (e.g.,
<hotwire-toast>,<dynamic-button>,<hotwire-location>) - Stimulus controllers in
core/hotwire/controllers/ - Utilities in
core/src/utils/utils.ts
- Web Components (e.g.,
- @bagisto-native/react
- Thin React wrappers that render the hidden web components
- Native side
- Bridge components implemented using
@hotwired/hotwire-native-bridge
- Bridge components implemented using
Note: The
bundle.jsfile from@bagisto-native/core/public/hotwiremust be included in your app’spublic/folder and loaded on pages that use native features.
Message Flow — Web → Native ➡️
- Web code calls a utility or sets a
data-bridge-*attribute on a hidden web component. - The utility locates the element wired to a Stimulus controller, sets attributes (e.g.,
data-bridge-message) and triggers a.click()(or dispatches an event). - The Stimulus controller picks this up and calls
this.send(...)through the Hotwire native bridge. - The native Bridge Component receives the message and performs the native action (toast, open cart, scan, etc.).
Example (utility usage):
import { triggerHotwireNativeToast } from "@bagisto-native/core";
triggerHotwireNativeToast("Profile updated!");Under the hood:
document.querySelector("[data-controller='bridge--toast']")
.setAttribute('data-bridge-message', message);
document.querySelector(...).click();
// Stimulus controller handles click -> this.send("show", {message})Message Flow — Native → Web ⬅️
- A native action occurs (user scanned a barcode, tapped share, etc.).
- The native Bridge Component sends data back or triggers a web-visible response.
- The web app receives a
CustomEvent(e.g.,turbo:next-search) or the Stimulus controller dispatches an event that the app listens for.
Common events emitted to web:
turbo:next-search→ { query?, code? } (search / scan results)turbo:next-cart-modal→ (open cart modal)turbo:hotwire-app-fill-addresses→ { data } (location reverse-geocode results)turbo:next-history-sync→ { URL } (history sync)
Example (listening to native search):
window.addEventListener("turbo:next-search", (e) => {
const customEvent = e as CustomEvent<{ query?: string; code?: string }>;
const query = customEvent.detail.query || customEvent.detail.code;
// handle the query (router push / filter)
});Sequence Example — History Sync 🔁
- Web detects a route change (e.g., Next.js
usePathname). - Web calls:
triggerHistorySyncEvent(new URL(window.location.href));- Utility finds the
bridge--historysyncelement and dispatchesturbo:next-history-syncwith detail{ url }. - The Stimulus controller
bridge--historysynchandles the event and callsthis.send("history", { location: url.toString() })to the native layer.
Events & Utilities Reference (quick) ⚡
Utilities (from @bagisto-native/core):
isTurboNativeUserAgent(userAgent?: string): booleantriggerHotwireNativeToast(message: string): voidtriggerHistorySyncEvent(url: URL): voidtriggerThemeModeEvent(mode: 'light' | 'dark'): voidtriggerCartCountValue(cartcount: number): void
Events to watch for:
turbo:next-search— native → web (search)turbo:next-cart-modal— native → web (open cart)turbo:hotwire-app-fill-addresses— native → web (location)turbo:next-history-sync— web ↔ native (history sync)
Quick copyable snippets ✅
Minimal toast trigger:
import { triggerHotwireNativeToast } from "@bagisto-native/core";
triggerHotwireNativeToast("Saved!");History sync (Next.js):
import { useEffect } from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { triggerHistorySyncEvent } from "@bagisto-native/core";
export default function HistorySync() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
const origin = window.location.origin;
const search = searchParams.toString() ? "?" + searchParams.toString() : "";
const url = new URL(origin + pathname + search);
triggerHistorySyncEvent(url);
}, [pathname, searchParams]);
return null;
}Listen to native search event:
useEffect(() => {
const handleTurboSearch = (e: Event) => {
const customEvent = e as CustomEvent<{ query?: string; code?: string }>;
const query = customEvent.detail.query || customEvent.detail.code;
if (!query) return;
router.push("/search?q=" + encodeURIComponent(query));
};
window.addEventListener("turbo:next-search", handleTurboSearch);
return () => window.removeEventListener("turbo:next-search", handleTurboSearch);
}, [router]);Integration Checklist ✅
- [ ] Install packages:
npm install @bagisto-native/core @bagisto-native/react- [ ] Copy
node_modules/@bagisto-native/core/public/hotwire/bundle.js→public/hotwire/bundle.jsand include it on all pages. - [ ] Render React wrapper components at the app root (hidden): e.g.,
<HotwireToast />,<HotwireHistorySync />,<DynamicButton />. - [ ] Add
windowevent listeners for:turbo:next-searchturbo:next-cart-modalturbo:hotwire-app-fill-addressesturbo:next-history-sync
- [ ] Use utilities to trigger native interactions from app logic.
- [ ] Use
isTurboNativeUserAgent()where behavior should be gated to native contexts.
Best Practices & Troubleshooting 🧭
- Keep bridge elements hidden (e.g.,
style={{ display: 'none' }}) — they are anchors only. - Utilities are SSR-safe (they check
documentfirst). Use them in client-only code. - Ensure
bundle.jsis included; missing bundle is the most common cause of communication failures. - Add console logs inside Stimulus controllers (or on native side) when diagnosing event flows.
- If events don’t appear in the web, confirm the native Bridge Component is sending data and the web's
windowlistener is attached.
Tip: For debugging, temporarily show the web components (remove
display:none) and addconsole.loginside event handlers to observe attribute changes and dispatched events.
Done. This file documents the Web ↔ Native communication flow with diagrams, code examples, and an integration checklist.
Next Steps
- Learn about Roles of Components
- Explore Hotwire & Turbo Native
- Understand Why WebView + Native Bridge
