diff --git a/src/components/Home/Home.tsx b/src/components/Home/Home.tsx
index 229914d..a301980 100644
--- a/src/components/Home/Home.tsx
+++ b/src/components/Home/Home.tsx
@@ -1,12 +1,11 @@
import styles from "@/components/Home/Home.module.scss";
+import { AppBridgeMessageType } from "@/components/provider/AppBridgeProvider/AppBridgeMessage.types";
+import { useAppBridge } from "@/components/provider/AppBridgeProvider/AppBridgeProvider";
import IconButton from "@/components/ui/IconButton/IconButton";
import Text from "@/components/ui/Text/Text";
-import { useRoute } from "@/hooks/common/useRoute";
-
const Home = () => {
- // 이후 네이티브 라우팅으로 변경
- const { navigateToReceiptEdit, navigateToRecognitionFail } = useRoute();
+ const { send } = useAppBridge();
return (
@@ -22,8 +21,16 @@ const Home = () => {
-
-
+ send({ type: AppBridgeMessageType.OPEN_GALLERY })}
+ />
+ send({ type: AppBridgeMessageType.OPEN_CAMERA })}
+ />
);
diff --git a/src/components/provider/AppBridgeProvider/AppBridgeMessage.types.ts b/src/components/provider/AppBridgeProvider/AppBridgeMessage.types.ts
new file mode 100644
index 0000000..425a3fe
--- /dev/null
+++ b/src/components/provider/AppBridgeProvider/AppBridgeMessage.types.ts
@@ -0,0 +1,40 @@
+export enum AppBridgeMessageType {
+ OPEN_CAMERA = "openCamera",
+ OPEN_GALLERY = "openGallery",
+ SHARE = "share",
+ CREATE_REVIEW = "createReview",
+ COPY = "copy",
+}
+
+export type AppBridgeMessage =
+ | OpenCameraMessage
+ | OpenGalleryMessage
+ | ShareMessage
+ | CreateReviewMessage
+ | CopyMessage;
+
+export interface OpenCameraMessage {
+ type: AppBridgeMessageType.OPEN_CAMERA;
+}
+
+export interface OpenGalleryMessage {
+ type: AppBridgeMessageType.OPEN_GALLERY;
+}
+
+export interface ShareMessage {
+ type: AppBridgeMessageType.SHARE;
+}
+
+export interface CreateReviewMessage {
+ type: AppBridgeMessageType.CREATE_REVIEW;
+ payload: {
+ json: string;
+ };
+}
+
+export interface CopyMessage {
+ type: AppBridgeMessageType.COPY;
+ payload: {
+ json: string;
+ };
+}
diff --git a/src/components/provider/AppBridgeProvider/AppBridgeProvider.tsx b/src/components/provider/AppBridgeProvider/AppBridgeProvider.tsx
new file mode 100644
index 0000000..b109d02
--- /dev/null
+++ b/src/components/provider/AppBridgeProvider/AppBridgeProvider.tsx
@@ -0,0 +1,46 @@
+import type { ReactNode } from "react";
+import { createContext, useContext } from "react";
+
+import type { AppBridgeMessage } from "@/components/provider/AppBridgeProvider/AppBridgeMessage.types";
+import {
+ convertToAndroidAppBridge,
+ convertToIOSAppBridge,
+} from "@/components/provider/AppBridgeProvider/convertToNativeMessage";
+import { useUserAgent } from "@/components/provider/UserAgentProvider";
+
+interface AppBridgeProviderProps {
+ children: ReactNode;
+}
+
+interface AppBridge {
+ send: (message: AppBridgeMessage) => void;
+}
+
+export const AppBridgeContext = createContext(null);
+
+export function AppBridgeProvider({ children }: AppBridgeProviderProps) {
+ const userAgent = useUserAgent();
+
+ const isIOS = userAgent.isIOS;
+
+ const send = (message: AppBridgeMessage) => {
+ try {
+ if (isIOS) return convertToIOSAppBridge(message);
+ return convertToAndroidAppBridge(message);
+ } catch {
+ alert("App Bridge API called: " + message.type);
+ }
+ };
+
+ return {children};
+}
+
+export function useAppBridge() {
+ const appBridge = useContext(AppBridgeContext);
+
+ if (appBridge == null) {
+ throw new Error("Wrap App Bridge Provider");
+ }
+
+ return appBridge;
+}
diff --git a/src/components/provider/AppBridgeProvider/convertToNativeMessage.ts b/src/components/provider/AppBridgeProvider/convertToNativeMessage.ts
new file mode 100644
index 0000000..8434239
--- /dev/null
+++ b/src/components/provider/AppBridgeProvider/convertToNativeMessage.ts
@@ -0,0 +1,45 @@
+import { AppBridgeMessageType } from "@/components/provider/AppBridgeProvider/AppBridgeMessage.types";
+import type { AppBridgeMessage } from "@/components/provider/AppBridgeProvider/AppBridgeMessage.types";
+
+const iosHandlers = {
+ [AppBridgeMessageType.OPEN_CAMERA]: () => window.webkit?.messageHandlers.openCamera.postMessage(),
+ [AppBridgeMessageType.OPEN_GALLERY]: () =>
+ window.webkit?.messageHandlers.openGallery.postMessage(),
+ [AppBridgeMessageType.SHARE]: () => window.webkit?.messageHandlers.share.postMessage(),
+ [AppBridgeMessageType.CREATE_REVIEW]: (message: { payload: { json: string } }) =>
+ window.webkit?.messageHandlers.createReview.postMessage(message.payload.json),
+ [AppBridgeMessageType.COPY]: (message: { payload: { json: string } }) =>
+ window.webkit?.messageHandlers.copy.postMessage(message.payload.json),
+};
+
+const androidHandlers = {
+ [AppBridgeMessageType.OPEN_CAMERA]: () => window.AndroidBridge?.openCamera(),
+ [AppBridgeMessageType.OPEN_GALLERY]: () => window.AndroidBridge?.openGallery(),
+ [AppBridgeMessageType.SHARE]: () => window.AndroidBridge?.share(),
+ [AppBridgeMessageType.CREATE_REVIEW]: (message: { payload: { json: string } }) =>
+ window.AndroidBridge?.createReview(message.payload.json),
+ [AppBridgeMessageType.COPY]: (message: { payload: { json: string } }) =>
+ window.AndroidBridge?.copy(message.payload.json),
+};
+
+export function convertToIOSAppBridge(message: AppBridgeMessage) {
+ const handler = iosHandlers[message.type];
+
+ if (handler) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ handler(message as any);
+ } else {
+ console.warn("Unhandled message type:", message.type);
+ }
+}
+
+export function convertToAndroidAppBridge(message: AppBridgeMessage) {
+ const handler = androidHandlers[message.type];
+
+ if (handler) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ handler(message as any);
+ } else {
+ console.warn("Unhandled message type:", message.type);
+ }
+}
diff --git a/src/components/provider/UserAgentProvider.tsx b/src/components/provider/UserAgentProvider.tsx
new file mode 100644
index 0000000..1c4a8de
--- /dev/null
+++ b/src/components/provider/UserAgentProvider.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import type { ReactNode } from "react";
+import { createContext, useContext, useEffect, useState } from "react";
+
+export interface UserAgent {
+ rawUA: string;
+ isIOS: boolean;
+ isAndroid: boolean;
+ isMobile: boolean;
+}
+
+export const UserAgentContext = createContext(null);
+
+export function UserAgentProvider({ children }: { children: ReactNode }) {
+ const [userAgent, setUserAgent] = useState({
+ isAndroid: false,
+ isIOS: true,
+ rawUA: "",
+ isMobile: true,
+ });
+
+ useEffect(() => {
+ const _userAgent = navigator.userAgent.toLowerCase();
+
+ const isMobile = _userAgent.indexOf("iphone") > -1 || _userAgent.indexOf("android") > -1;
+
+ if (_userAgent.indexOf("android") > -1) {
+ setUserAgent({
+ isIOS: false,
+ isAndroid: true,
+ rawUA: _userAgent,
+ isMobile,
+ });
+ } else {
+ setUserAgent({
+ isIOS: true,
+ isAndroid: false,
+ rawUA: _userAgent,
+ isMobile,
+ });
+ }
+ }, []);
+
+ return {children};
+}
+
+export function useUserAgent() {
+ const userAgent = useContext(UserAgentContext);
+
+ if (userAgent == null) {
+ throw new Error("Wrap UserAgent Provider");
+ }
+ return userAgent;
+}
diff --git a/src/main.tsx b/src/main.tsx
index ae5863d..3a49d45 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -5,7 +5,9 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import AppRouter from "@/router/AppRouter";
+import { AppBridgeProvider } from "@/components/provider/AppBridgeProvider/AppBridgeProvider";
import ReactQueryClientProvider from "@/components/provider/ReactQueryClientProvider";
+import { UserAgentProvider } from "@/components/provider/UserAgentProvider";
import "@/styles/reset.scss";
import "@/styles/global.scss";
@@ -13,8 +15,12 @@ import "@/styles/global.scss";
ReactDom.createRoot(document.getElementById("root")!).render(
-
-
+
+
+
+
+
+
,
);
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
index b78db39..c1d4d45 100644
--- a/src/types/global.d.ts
+++ b/src/types/global.d.ts
@@ -2,3 +2,30 @@ declare module "*.module.scss" {
const classes: { [className: string]: string };
export default classes;
}
+
+export {};
+
+type MessageHandler = {
+ postMessage: (message?: T) => void;
+};
+
+declare global {
+ interface Window {
+ webkit?: {
+ messageHandlers: {
+ openCamera: MessageHandler;
+ openGallery: MessageHandler;
+ share: MessageHandler;
+ createReview: MessageHandler;
+ copy: MessageHandler;
+ };
+ };
+ AndroidBridge?: {
+ openCamera: () => void;
+ openGallery: () => void;
+ share: () => void;
+ createReview: (json: string) => void;
+ copy: (json: string) => void;
+ };
+ }
+}