Skip to content

Commit 6bd3412

Browse files
committed
feat: app bridge provider, user agent provider 추가
1 parent 831e247 commit 6bd3412

File tree

5 files changed

+211
-0
lines changed

5 files changed

+211
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export enum AppBridgeMessageType {
2+
OPEN_CAMERA = "openCamera",
3+
OPEN_GALLERY = "openGallery",
4+
SHARE = "share",
5+
CREATE_REVIEW = "createReview",
6+
COPY = "copy",
7+
}
8+
9+
export type AppBridgeMessage =
10+
| OpenCameraMessage
11+
| OpenGalleryMessage
12+
| ShareMessage
13+
| CreateReviewMessage
14+
| CopyMessage;
15+
16+
export interface OpenCameraMessage {
17+
type: AppBridgeMessageType.OPEN_CAMERA;
18+
}
19+
20+
export interface OpenGalleryMessage {
21+
type: AppBridgeMessageType.OPEN_GALLERY;
22+
}
23+
24+
export interface ShareMessage {
25+
type: AppBridgeMessageType.SHARE;
26+
}
27+
28+
export interface CreateReviewMessage {
29+
type: AppBridgeMessageType.CREATE_REVIEW;
30+
payload: {
31+
json: string;
32+
};
33+
}
34+
35+
export interface CopyMessage {
36+
type: AppBridgeMessageType.COPY;
37+
payload: {
38+
json: string;
39+
};
40+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { ReactNode } from "react";
2+
import { createContext, useContext } from "react";
3+
4+
import type { AppBridgeMessage } from "@/components/provider/AppBridgeProvider/AppBridgeMessage.types";
5+
import {
6+
convertToAndroidAppBridge,
7+
convertToIOSAppBridge,
8+
} from "@/components/provider/AppBridgeProvider/convertToNativeMessage";
9+
import { useUserAgent } from "@/components/provider/UserAgentProvider";
10+
11+
interface AppBridgeProviderProps {
12+
children: ReactNode;
13+
}
14+
15+
interface AppBridge {
16+
send: (message: AppBridgeMessage) => void;
17+
}
18+
19+
export const AppBridgeContext = createContext<null | AppBridge>(null);
20+
21+
export function AppBridgeProvider({ children }: AppBridgeProviderProps) {
22+
const userAgent = useUserAgent();
23+
24+
const isIOS = userAgent.isIOS;
25+
26+
const send = (message: AppBridgeMessage) => {
27+
try {
28+
if (isIOS) return convertToIOSAppBridge(message);
29+
return convertToAndroidAppBridge(message);
30+
} catch {
31+
alert("App Bridge API called: " + message.type);
32+
}
33+
};
34+
35+
return <AppBridgeContext.Provider value={{ send }}>{children}</AppBridgeContext.Provider>;
36+
}
37+
38+
export function useAppBridge() {
39+
const appBridge = useContext(AppBridgeContext);
40+
41+
if (appBridge == null) {
42+
throw new Error("Wrap App Bridge Provider");
43+
}
44+
45+
return appBridge;
46+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { AppBridgeMessageType } from "@/components/provider/AppBridgeProvider/AppBridgeMessage.types";
2+
import type { AppBridgeMessage } from "@/components/provider/AppBridgeProvider/AppBridgeMessage.types";
3+
4+
const iosHandlers = {
5+
[AppBridgeMessageType.OPEN_CAMERA]: () => window.webkit?.messageHandlers.openCamera.postMessage(),
6+
[AppBridgeMessageType.OPEN_GALLERY]: () =>
7+
window.webkit?.messageHandlers.openGallery.postMessage(),
8+
[AppBridgeMessageType.SHARE]: () => window.webkit?.messageHandlers.share.postMessage(),
9+
[AppBridgeMessageType.CREATE_REVIEW]: (message: { payload: { json: string } }) =>
10+
window.webkit?.messageHandlers.createReview.postMessage(message.payload.json),
11+
[AppBridgeMessageType.COPY]: (message: { payload: { json: string } }) =>
12+
window.webkit?.messageHandlers.copy.postMessage(message.payload.json),
13+
};
14+
15+
const androidHandlers = {
16+
[AppBridgeMessageType.OPEN_CAMERA]: () => window.AndroidBridge?.openCamera(),
17+
[AppBridgeMessageType.OPEN_GALLERY]: () => window.AndroidBridge?.openGallery(),
18+
[AppBridgeMessageType.SHARE]: () => window.AndroidBridge?.share(),
19+
[AppBridgeMessageType.CREATE_REVIEW]: (message: { payload: { json: string } }) =>
20+
window.AndroidBridge?.createReview(message.payload.json),
21+
[AppBridgeMessageType.COPY]: (message: { payload: { json: string } }) =>
22+
window.AndroidBridge?.copy(message.payload.json),
23+
};
24+
25+
export function convertToIOSAppBridge(message: AppBridgeMessage) {
26+
const handler = iosHandlers[message.type];
27+
if (handler) {
28+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
29+
handler(message as any);
30+
} else {
31+
console.warn("Unhandled message type:", message.type);
32+
}
33+
}
34+
35+
export function convertToAndroidAppBridge(message: AppBridgeMessage) {
36+
const handler = androidHandlers[message.type];
37+
if (handler) {
38+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
39+
handler(message as any);
40+
} else {
41+
console.warn("Unhandled message type:", message.type);
42+
}
43+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"use client";
2+
3+
import type { ReactNode } from "react";
4+
import { createContext, useContext, useEffect, useState } from "react";
5+
6+
export interface UserAgent {
7+
rawUA: string;
8+
isIOS: boolean;
9+
isAndroid: boolean;
10+
isMobile: boolean;
11+
}
12+
13+
export const UserAgentContext = createContext<UserAgent | null>(null);
14+
15+
export function UserAgentProvider({ children }: { children: ReactNode }) {
16+
const [userAgent, setUserAgent] = useState<UserAgent>({
17+
isAndroid: false,
18+
isIOS: true,
19+
rawUA: "",
20+
isMobile: true,
21+
});
22+
23+
useEffect(() => {
24+
const _userAgent = navigator.userAgent.toLowerCase();
25+
26+
const isMobile = _userAgent.indexOf("iphone") > -1 || _userAgent.indexOf("android") > -1;
27+
28+
if (_userAgent.indexOf("android") > -1) {
29+
setUserAgent({
30+
isIOS: false,
31+
isAndroid: true,
32+
rawUA: _userAgent,
33+
isMobile,
34+
});
35+
} else {
36+
setUserAgent({
37+
isIOS: true,
38+
isAndroid: false,
39+
rawUA: _userAgent,
40+
isMobile,
41+
});
42+
}
43+
}, []);
44+
45+
return <UserAgentContext.Provider value={userAgent}>{children}</UserAgentContext.Provider>;
46+
}
47+
48+
export function useUserAgent() {
49+
const userAgent = useContext(UserAgentContext);
50+
51+
if (userAgent == null) {
52+
throw new Error("Wrap UserAgent Provider");
53+
}
54+
return userAgent;
55+
}

src/types/global.d.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,30 @@ declare module "*.module.scss" {
22
const classes: { [className: string]: string };
33
export default classes;
44
}
5+
6+
export {};
7+
8+
type MessageHandler<T = void> = {
9+
postMessage: (message?: T) => void;
10+
};
11+
12+
declare global {
13+
interface Window {
14+
webkit?: {
15+
messageHandlers: {
16+
openCamera: MessageHandler;
17+
openGallery: MessageHandler;
18+
share: MessageHandler;
19+
createReview: MessageHandler<string>;
20+
copy: MessageHandler<string>;
21+
};
22+
};
23+
AndroidBridge?: {
24+
openCamera: () => void;
25+
openGallery: () => void;
26+
share: () => void;
27+
createReview: (json: string) => void;
28+
copy: (json: string) => void;
29+
};
30+
}
31+
}

0 commit comments

Comments
 (0)