Skip to content

Commit

Permalink
feat: app bridge provider, user agent provider 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
sikkzz committed Feb 5, 2025
1 parent 831e247 commit 6bd3412
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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;
};
}
46 changes: 46 additions & 0 deletions src/components/provider/AppBridgeProvider/AppBridgeProvider.tsx
Original file line number Diff line number Diff line change
@@ -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 | AppBridge>(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 <AppBridgeContext.Provider value={{ send }}>{children}</AppBridgeContext.Provider>;
}

export function useAppBridge() {
const appBridge = useContext(AppBridgeContext);

if (appBridge == null) {
throw new Error("Wrap App Bridge Provider");
}

return appBridge;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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);
}
}
55 changes: 55 additions & 0 deletions src/components/provider/UserAgentProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<UserAgent | null>(null);

export function UserAgentProvider({ children }: { children: ReactNode }) {
const [userAgent, setUserAgent] = useState<UserAgent>({
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 <UserAgentContext.Provider value={userAgent}>{children}</UserAgentContext.Provider>;
}

export function useUserAgent() {
const userAgent = useContext(UserAgentContext);

if (userAgent == null) {
throw new Error("Wrap UserAgent Provider");
}
return userAgent;
}
27 changes: 27 additions & 0 deletions src/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,30 @@ declare module "*.module.scss" {
const classes: { [className: string]: string };
export default classes;
}

export {};

type MessageHandler<T = void> = {
postMessage: (message?: T) => void;
};

declare global {
interface Window {
webkit?: {
messageHandlers: {
openCamera: MessageHandler;
openGallery: MessageHandler;
share: MessageHandler;
createReview: MessageHandler<string>;
copy: MessageHandler<string>;
};
};
AndroidBridge?: {
openCamera: () => void;
openGallery: () => void;
share: () => void;
createReview: (json: string) => void;
copy: (json: string) => void;
};
}
}

0 comments on commit 6bd3412

Please sign in to comment.