์น๋ทฐ ๋ธ๋ฆฟ์ง ๊ฐ์ ๊ธฐ: ๋ฒ์ ๋ถ๊ธฐ ์ง์ฅ์์ ๋ฒ์ด๋๊ธฐ
- #WebView
- #Web
- #App
- #Bridge Interface
๋ค์ด๊ฐ๋ฉฐ
์๋ ํ์ธ์, ๋ ธ๋จธ์ค Android ๊ฐ๋ฐ์ ์์์ง์ ๋๋ค. ์น๋ทฐ ํ๊ฒฝ์์๋ ์น๊ณผ ๋ค์ดํฐ๋ธ ์ฑ์ด ์๋ก ํต์ ํ๊ธฐ ์ํ ๋ธ๋ฆฟ์ง(Bridge) ์ธํฐํ์ด์ค๊ฐ ํ์์ ์ธ๋ฐ์, ๊ธฐ์กด ๋ธ๋ฆฟ์ง ์ฒด๊ณ๋ฅผ ์ด์ํ๋ฉด์ ์ฌ๋ฌ ๋ถํธํจ๊ณผ ๊ตฌ์กฐ์ ํ๊ณ๋ฅผ ๋๋ผ๊ฒ ๋์์ต๋๋ค.
์ด ๊ธ์์๋ ๊ธฐ์กด ๋ธ๋ฆฟ์ง ๊ตฌ์กฐ์์ ์ด๋ค ๋ฌธ์ ๊ฐ ์์๋์ง ๋ถ์ํ๊ณ , ๋จ์ผ ๋ธ๋ฆฟ์ง ํจ์ + type ๊ธฐ๋ฐ ๋ถ๊ธฐ ์ฒ๋ฆฌ ๋ฐฉ์์ผ๋ก ์ฌ์ค๊ณํ ๊ณผ์ ์ ๊ณต์ ํ๋ ค ํฉ๋๋ค.
1. ๊ธฐ์กด ๊ตฌ์กฐ์ ๋ฌธ์ ์
๊ฐ์ ์์ ์ ์์, ๊ธฐ์กด ๋ธ๋ฆฟ์ง ์ฒด๊ณ์์ ๊ฒช๊ณ ์๋ ๋ฌธ์ ์ ๋ค์ ์ ๋ฆฌํ์ต๋๋ค.
1-1. ๋ฒ์ ๋ถ๊ธฐ ๋ก์ง์ ์ด์ค ์ฐ์ฌ
๊ฐ์ฅ ํฌ๊ฒ ์ฒด๊ฐํ๋ ๋ฌธ์ ๋ ๋ฒ์ ๋ถ๊ธฐ ๋ก์ง์ด ์ฌ๊ธฐ์ ๊ธฐ ํฉ์ด์ ธ ์๋ค๋ ๊ฒ์ด์์ต๋๋ค. ์น์์ ๋ค์ดํฐ๋ธ ์ปค๋งจ๋๋ฅผ ํธ์ถํ ๋, checkAppVersion์ผ๋ก ์ฑ ๋ฒ์ ์ ํ์ธํ๋ ๋ก์ง์ด ํธ์ถ๋ถ์ runCommand.ts ๋ด๋ถ ๋ ๊ณณ์์ ์ค๋ณต์ผ๋ก ์กด์ฌํ์ต๋๋ค.
if (checkAppVersion(VersionDict.MultiImage)) {
return runCommand({ key: 'openEditPostFanFeed', postId, channelId, ... });
}
showVersionUpdateAlert(() => {
if (checkAppVersion('1.29.0')) {
runCommand({ key: 'openEditPostFanFeed', postId, channelId, ... });
return;
}
// 1.29.0 ๋ฏธ๋ง: ์น ์๋ํฐ ๋ค์ด์ผ๋ก๊ทธ๋ก ํด๋ฐฑ
showDialog(<FeedPostEditPage post={post} />);
});
// runCommand.ts ๋ด๋ถ โ ๊ฐ์ ์ปค๋งจ๋์ ๋ํด ๋ฒ์ ๋ณ๋ก ๋ค๋ฅธ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ์ก
ios({ postId, channelId, boardId, hasMembership }) {
if (checkAppVersion(VersionDict.MultiImage)) { // 1.37.0+
return postMessage({ data: JSON.stringify({ postId, channelId, boardId, hasMembership }) });
}
if (checkAppVersion(VersionDict.Board)) { // 1.31.0+
return postMessage({ data: JSON.stringify({ postId, channelId, boardId }) });
}
postMessage({ data: JSON.stringify({ postId, channelId }) }); // ๊ทธ ์ดํ
}
์ด๋ฐ ์์ ๋ฒ์ ๋ถ๊ธฐ๊ฐ ์ฌ๋ฌ ํ์ผ์ ๊ฑธ์ณ ์ฐ์ฌํด ์์๊ณ , ํ๋์ฝ๋ฉ๋ ๋ฒ์ ๋ฌธ์์ด๋ ๊ณณ๊ณณ์ ํฉ์ด์ ธ ์์์ต๋๋ค.
1-2. ๊ทผ๋ณธ ์์ธ โ ์๋ฌ ํธ๋ค๋ง์ ๋ถ์ฌ
๊ทธ๋ฐ๋ฐ ์ ์ด๋ ๊ฒ ๋ฒ์ ๋ถ๊ธฐ๊ฐ ๊ณณ๊ณณ์ ์๊ฒจ๋ ๊ฑธ๊น์? ๊ทผ๋ณธ ์์ธ์ ์ ํ๋ซํผ ๋ชจ๋ ์น์์ ๋ฏธ์ง์ ์ปค๋งจ๋๋ฅผ ํธ์ถํ์ ๋ ์๋ฌ๋ฅผ ๋ฐํํ๋ ๋ฉ์ปค๋์ฆ์ด ์์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ฑ์ด โ์ด ์ปค๋งจ๋๋ ๋ชจ๋ฅธ๋คโ๊ณ ์๋ ค์ค ์ ์์ผ๋, ์น์์ ๋ฏธ๋ฆฌ ๋ฒ์ ์ ํ์ธํด ๋ฐฉ์ดํ ์๋ฐ์ ์์๋ ๊ฒ์ด์ฃ .
ํ๋ซํผ๋ณ๋ก ์คํจ ์์๋ ๋ฌ๋์ต๋๋ค.
Android: ์ปค๋งจ๋๋ณ ๊ฐ๋ณ ๋ฉ์๋ ๋ฐฉ์
Android๋ ์ปค๋งจ๋๋ง๋ค ๋ณ๋์ @JavascriptInterface ๋ฉ์๋๋ก ์ ์ํ๋ ๊ตฌ์กฐ์
๋๋ค.
internal class InAppBrowserJsInterface(...) : FrommAppJsInterface {
@JavascriptInterface
override fun openBrowser(url: String) {
viewModel.action(InAppBrowserAction.OpenBrowser(url))
}
@JavascriptInterface
override fun openEditPostFanFeed(data: String) {
val jsonString = JSONObject(data)
// ... ํ์ฑ ํ ์ฒ๋ฆฌ
}
// ๊ฐ ์ปค๋งจ๋๋ง๋ค ๊ฐ๋ณ ๋ฉ์๋
// 'newFeature' ๊ฐ์ ์ ์ปค๋งจ๋ ๋ฉ์๋๋ ์กด์ฌํ์ง ์์
}
์น์์ ๋ฏธ์ง์ ์ปค๋งจ๋๋ฅผ ํธ์ถํ๋ฉด, ํด๋น ๋ฉ์๋๊ฐ ๋ค์ดํฐ๋ธ์ ์กด์ฌํ์ง ์์ผ๋ฏ๋ก ์น๋ทฐ์ JavaScript์์ TypeError๊ฐ ๋ฐ์ํฉ๋๋ค. ๋ค์ดํฐ๋ธ ์ชฝ์๋ ํธ์ถ ์์ฒด๊ฐ ๋๋ฌํ์ง ์์ ์๋ฌด ์ผ๋ ์ผ์ด๋์ง ์๊ณ , ์น์์๋ ์ด๋ฅผ catchํ๊ณ ์์ง ์์ ๋ฏธ์ง์์ธ์ง ์ฝ๋ ๋ฒ๊ทธ์ธ์ง ๊ตฌ๋ถํ ์ ์์์ต๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ์ธ์ํ๊ณ ์์๊ธฐ์, ์ฌ์ฉํ์ง ์๋ ์ปค๋งจ๋์ ๋น ๋ฉ์๋๋ฅผ ์ ์งํ๋ ๋ฐฉ์ด ํจํด๋ ์กด์ฌํ์ต๋๋ค.
@JavascriptInterface
override fun refreshHome() {
//do nothing, ์น์์ ์์ง refreshHome ํธ์ถ์ ์ ๊ฑฐํ์ง ์์ ์น์ชฝ ์ค๋ฅ ๋ฐ์ํ์ง ์๋๋ก
}
์น์์ ํธ์ถํ๋ ๋ฉ์๋๊ฐ ์์ผ๋ฉด JavaScript ์๋ฌ๊ฐ ๋ฐ์ํ๋ฏ๋ก, ์ฑ์์ ๋น ๋ฉ์๋๋ฅผ ์ ์งํด์ผ ํ์ต๋๋ค. ์ด๋ ์ฑ/์น ๊ฐ ๋ฐฐํฌ ์์์ ์๋ฌต์ ์ธ ์์กด์ ๋ง๋๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณ์์ต๋๋ค. ๋ํ ์น์์ ์ฌ์ฉํ์ง ์๋ ์ปค๋งจ๋์ผ์ง๋ผ๋, ๊ทธ๊ฒ์ด ์ญ์ ๋ ๊ฒ์ด ๋ชจ๋ฐ์ผ ๊ฐ๋ฐ์๊ฐ ์ธ์งํ๊ธฐ๋ ์ด๋ ค์ ๋ฐฉ์น๋ ๋ถ๋ถ๋ ์์์ต๋๋ค.
iOS: ๋จ์ผ ์ง์ ์ + switch โ default: break
iOS๋ WKScriptMessageHandlerWithReply ํ๋กํ ์ฝ์ ์ฌ์ฉํ๊ณ ์์์ต๋๋ค. ์น์์ postMessage๋ฅผ ํธ์ถํ๋ฉด JavaScript Promise๊ฐ ๋ฐํ๋๊ณ , iOS๊ฐ replyHandler๋ฅผ ํธ์ถํ๋ฉด ๊ทธ Promise๊ฐ resolve/reject๋๋ ๊ตฌ์กฐ์
๋๋ค.
switch command {
case "isNotificationEnable":
checkNotificationPermission { isEnabled in
replyHandler(isEnabled, nil) // Promise resolve
}
case "openBrowser":
UIApplication.shared.open(url)
// replyHandler ํธ์ถ ์ ํจ โ Promise pending
default:
break
// ๋ฏธ์ง์ ์ปค๋งจ๋๋ replyHandler ํธ์ถ ์ ํจ โ Promise pending
}
๊ฐ์ ๋ฐํํ๋ ์ผ๋ถ ์ปค๋งจ๋๋ง replyHandler๋ฅผ ํธ์ถํ๊ณ ์์๊ณ , ๋๋จธ์ง ์ปค๋งจ๋์ ๋ฏธ์ง์ ์ปค๋งจ๋๋ replyHandler๋ฅผ ํธ์ถํ์ง ์์ Promise๊ฐ ์์ํ pending ์ํ์ ๋น ์ก์ต๋๋ค. ํ์ฌ๋ ๋๋ถ๋ถ์ ์ปค๋งจ๋์์ awaitํ์ง ์์ ๋น์ฅ ๋ฌธ์ ๊ฐ ๋์ง ์์์ง๋ง, ์น์์ ๋ฏธ์ง์ ์ฌ๋ถ๋ฅผ ์ ๋ฐฉ๋ฒ์ด ์ ํ ์๋ ์ํ์์ต๋๋ค.
๊ฒฐ๊ตญ, ์ฑ์ด ๋ฏธ์ง์ type์ ๋ํด ์๋ฌ๋ฅผ ๋ฐํํ ์ ์๋ค๋ฉด ์น์ ๋ฒ์ ๋ถ๊ธฐ ๋ก์ง์ ์ ๊ฑฐํ๊ณ ์ผ๊ด๋ ์ ๋ฐ์ดํธ ํ์ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ง๋๋ค. ๊ทธ๋์ ์ด ์๋ฌ ํธ๋ค๋ง ๋ถ์ฌ๋ฅผ ๊ฐ์ฅ ๋จผ์ ํด๊ฒฐํ๊ธฐ๋ก ํ์ต๋๋ค.
2. ๋จ์ผ ๋ธ๋ฆฟ์ง ํจ์๋ก์ ์ ํ
๊ธฐ์กด์๋ ์ปค๋งจ๋๋ง๋ค ๊ฐ๋ณ ํจ์/๋ฉ์๋๋ก ๋ถ๋ฆฌ๋์ด ์๋ ๊ตฌ์กฐ๋ฅผ, ํ๋์ ์ง์
์ (sendBridge)์์ type ํ๋ ๊ฐ์ ๋ฐ๋ผ ์ ์ ํ ์ปค๋งจ๋๋ฅผ ์คํํ๋ ๋ฐฉ์์ผ๋ก ์ ํํ์ต๋๋ค.
๋ฐ์ดํฐ ํ์ ์ ์
๋จผ์ ์น๊ณผ ๋ค์ดํฐ๋ธ๊ฐ ์ฃผ๊ณ ๋ฐ์ JSON ํ์์ ์ ์ํ์ต๋๋ค.
์์ฒญ (Web โ Native)
{
"type": "openBrowser",
"payload": {
"url": "https://example.com"
}
}
type(ํ์): ์คํํ ์ปค๋งจ๋ ์ด๋ฆpayload(์ ํ): ์ปค๋งจ๋์ ํ์ํ ํ๋ผ๋ฏธํฐ ๊ฐ์ฒด
์๋ต (Native โ Web)
์ฑ๊ณต ์:
{
"success": true,
"data": { "enabled": true } // optional
}
์คํจ ์:
{
"success": false,
"error": { "code": "UNKNOWN_TYPE" }
}
success ํ๋๋ก ์ฑ๊ณต/์คํจ๋ฅผ ๋ช
ํํ๊ฒ ๊ตฌ๋ถํ๊ณ , ์คํจ ์์๋ ์๋ฌ ์ฝ๋๋ฅผ ํจ๊ป ๋ฐํํฉ๋๋ค. ์๋ฌ ์ฝ๋๋ ์ธ ๊ฐ์ง๋ก ๊ตฌ๋ถ๋ฉ๋๋ค.
| ์ฝ๋ | ์๋ฏธ |
|---|---|
UNKNOWN_TYPE | ์ฑ์ด ํด๋น type ์์ฒด๋ฅผ ๋ชจ๋ฅด๋ ๊ฒฝ์ฐ |
UNSUPPORTED_TYPE | ์ฑ์ ์์ง๋ง ํ์ฌ ํ๋ฉด์์ ์ฒ๋ฆฌํ์ง ๋ชปํ๋ type์ธ ๊ฒฝ์ฐ |
INVALID_PARAMS | JSON ํ์ฑ ์คํจ ๋๋ ํ์ ํ๋ผ๋ฏธํฐ ๋๋ฝ |
์น ์ธก ๊ตฌํ
์ด ๋ฐ์ดํฐ ํ์์ ๋ฐํ์ผ๋ก, ์น์์๋ sendBridge๋ผ๋ ๋จ์ผ ํจ์๋ฅผ ํตํด ๋ชจ๋ ๋ค์ดํฐ๋ธ ํธ์ถ์ ์ํํฉ๋๋ค.
interface BridgeCommandMap {
openBrowser: (params: { url: string }) => void;
...
}
async function sendBridge<T extends keyof BridgeCommandMap>(
type: T,
...args: Parameters<BridgeCommandMap[T]> extends []
? []
: [payload: Parameters<BridgeCommandMap[T]>[0]]
): Promise<BridgeCommandReturnType<T>>
BridgeCommandMap ์ธํฐํ์ด์ค์ ๋ชจ๋ ์ปค๋งจ๋์ ํ๋ผ๋ฏธํฐ์ ๋ฐํ ํ์
์ ์ ์ํ์๊ณ , sendBridge ๋ฉ์๋๋ฅผ ํธ์ถ ํ ๋์๋ type safeํ๊ฒ ์ปค๋งจ๋๋ฅผ ํธ์ถํ ์ ์๋๋ก ๊ฐ์ ํ์ต๋๋ค.
// ํ๋ผ๋ฏธํฐ ์๋ ์ปค๋งจ๋
await sendBridge('pageLoadFinished');
// ํ๋ผ๋ฏธํฐ ์๋ ์ปค๋งจ๋
await sendBridge('openBrowser', { url: 'https://example.com' });
// ๋ฐํ๊ฐ ์๋ ์ปค๋งจ๋
const isEnabled = await sendBridge('isNotificationEnable');
ํ๋ซํผ๋ณ๋ก ๋ค์ดํฐ๋ธ์์ ํต์ ๋ฐฉ์์ด ๋ค๋ฅด์ง๋ง, sendBridge๊ฐ ์ด๋ฅผ ๋ด๋ถ์ ์ผ๋ก ์ถ์ํํฉ๋๋ค.
sendBridge ํธ์ถ
โ
โโ window.webkit?.messageHandlers?.Fromm โ iOS
โ postMessage({ command: 'sendBridge', data: JSON.stringify({ type, payload }) })
โ
โโ window.Fromm โ Android
โ window.Fromm.sendBridge(JSON.stringify({ type, payload }))
โ
โโ ๋ ๋ค ์์ โ ๊ฐ๋ฐ/๋ธ๋ผ์ฐ์ ํ๊ฒฝ
console.log ์ถ๋ ฅ ํ undefined ๋ฐํ
์๋ต์ด ์คํจ์ธ ๊ฒฝ์ฐ BridgeError๋ฅผ throwํ๋ฏ๋ก, ํธ์ถ๋ถ์์ ์๋ฌ๋ฅผ ํธ๋ค๋งํ ์ ์์ต๋๋ค. ๊ตฌ๋ฒ์ ์ฑ์์ ์
๋ฐ์ดํธ ํ์
๋
ธ์ถ์ด ํ์ํ ๊ฒฝ์ฐ, ์น์์๋ ๋ฒ์ ์ฒดํฌ ํ ํ์ ์์ดUNKNOWN_TYPE์ผ๋ก error code๊ฐ ๋ฐํ๋๋ ๊ฒ์ ๋ณด๊ณ ๋
ธ์ถํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
try {
await sendBridge('download', { url: '...', type: 'image' });
} catch (e) {
if (e instanceof BridgeError && e.code === 'UNKNOWN_TYPE') {
showUpdatePopup(); // ๊ตฌ๋ฒ์ ์ฑ ์
๋ฐ์ดํธ ํ์
๋
ธ์ถ
}
}
3. Android ๊ตฌํ
Android์์๋ sendBridge๋ผ๋ ๋จ์ผ @JavascriptInterface ๋ฉ์๋๋ง ๋
ธ์ถํ๊ณ , ๋ด๋ถ์ ์ผ๋ก ์๋ ๊ทธ๋ฆผ์ ํ๋ฆ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
์น๋ทฐ (JSON)
โ
โผ
BridgeCommandParser
โ JSON์ ํ์
์์ ํ ์ปค๋งจ๋ ๊ฐ์ฒด๋ก ๋ณํ
โ
โโ ํ์ฑ ์คํจ โ BridgeResponse.Error (INVALID_PARAMS / UNKNOWN_TYPE)
โ
โผ
BridgeCommand (sealed interface)
โ ์ฑ์ด ์ธ์ํ๋ ๋ชจ๋ ๋ธ๋ฆฟ์ง ๋ช
๋ น์ ๊ณตํต ํ์
โ
โโ BridgeHandler1 โโ ๋ชจ๋ ์ปค๋งจ๋ ์ฒ๋ฆฌ
โโ BridgeHandler2 โโ ์ผ๋ถ ์ปค๋งจ๋๋ง ์ฒ๋ฆฌ, ๋๋จธ์ง๋ UNSUPPORTED_TYPE
โ
โผ
BridgeResponse
โ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ toJson()์ผ๋ก ์ง๋ ฌํํ์ฌ ์น์ ๋ฐํ
โผ
์น๋ทฐ
BridgeCommand โ ์ปค๋งจ๋ ์ ์
์น์์ ๋ค์ดํฐ๋ธ๋ก ์์ฒญํ ์ ์๋ ๋ชจ๋ ๋ช
๋ น์ sealed interface๋ก ์ ์ํฉ๋๋ค. Parser๋ JSON์ ์ด ํ์
์ผ๋ก ๋ณํํ๊ณ , Handler๋ ์ด ํ์
์ ๋ฐ์ ์ฒ๋ฆฌํฉ๋๋ค.
sealed interface BridgeCommand {
data class OpenBrowser(val url: String) : BridgeCommand
data class OpenInAppBrowser(val url: String) : BridgeCommand
...
}
BridgeCommandParser โ Json ํ์ฑ
์น์์ ๋ณด๋ธ JSON ๋ฌธ์์ด์ BridgeCommand๋ก ๋ณํํฉ๋๋ค. ํ์๋ JSON์ด ์ ํจํ ์ปค๋งจ๋์ธ์ง๋ง ํ๋จํ๊ณ , ํด๋น ์น๋ทฐ๊ฐ ๊ทธ ์ปค๋งจ๋๋ฅผ ์ง์ํ๋์ง๋ ๊ด์ฌํ์ง ์์ต๋๋ค.
type์ ์ธ์ํ์ง ๋ชปํ๋ฉด โUNKNOWN_TYPE- JSON ํ์์ด ์๋ชป๋์๊ฑฐ๋ ํ์ ํ๋๊ฐ ๋๋ฝ๋๋ฉด โ
INVALID_PARAMS - ์ ์ ํ์ฑ โ
BridgeCommand๊ฐ์ฒด ์์ฑ
BridgeHandler โ ์น๋ทฐ ๋จ์์ ์ปค๋งจ๋ ๋์ ์ ์
ํ์ฑ๋ BridgeCommand๋ฅผ ์ค์ ๋ก ์คํํ๋ ์ฃผ์ฒด์
๋๋ค. ์ฌ๊ธฐ์ ๊ณ ๋ คํ ์ ์, ๋ค์ดํฐ๋ธ ์น๋ทฐ ํด๋์ค๋ง๋ค ๊ตฌํํ๋ ์ปค๋งจ๋ ๋ฒ์๊ฐ ๋ค๋ฅด๋ค๋ ์ ์
๋๋ค.
ํผ๋, ๋ผ์ด๋ธ, ๋ฏธ๋์ด ๋ฑ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์น๋ทฐ ํด๋์ค๊ฐ ์๋ ๋ฐ๋ฉด, ์ค์ ยท๊ณต์ง์ฌํญ ๋ฑ ๋จ์ ์ฝํ
์ธ ๋ฅผ ํ์ํ๋ ๊ณต์ฉ ์น๋ทฐ ํด๋์ค๊ฐ ์กด์ฌํฉ๋๋ค.
์๋ฅผ ๋ค์ด download๋ ํ์๊ฐ ์ ์์ ์ผ๋ก ํ์ฑํ์ง๋ง, ํด๋น ์ปค๋งจ๋๋ฅผ ๊ตฌํํ์ง ์์ ์น๋ทฐ ํด๋์ค์ ํธ๋ค๋ฌ์์๋ UNSUPPORTED_TYPE์ ๋ฐํํฉ๋๋ค. ์ด์ฒ๋ผ ๋์ผํ ์ปค๋งจ๋๋ผ๋ ์ด๋ ์น๋ทฐ ํด๋์ค์์ ์คํ๋๋๋์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง ์ ์์ด UNSUPPORTED_TYPE ํ์
์ด ์๊ฒจ๋ ๊ฒ์
๋๋ค.
internal class WebviewBridgeHandler(
...
) {
fun handle(command: BridgeCommand): BridgeResponse = when (command) {
is BridgeCommand.OpenBrowser -> {
// ๋ธ๋ผ์ฐ์ ์ด๊ธฐ ๋์
BridgeResponse.Success()
}
is BridgeCommand.OpenInAppBrowser -> {
// ์ธ์ฑ ๋ธ๋ผ์ฐ์ ์ด๊ธฐ ๋์
BridgeResponse.Success()
}
// BridgeCommand.Download ๋ฑ ๋ฏธ์ง์ type์ ๊ฒฝ์ฐ
else -> BridgeResponse.Error(BridgeErrorCode.UNSUPPORTED_TYPE)
}
BridgeResponse
๋ชจ๋ ํธ๋ค๋ฌ๋ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ BridgeResponse๋ก ๋ฐํํ๋ฉฐ, toJson()์ ํตํด JSON ๋ฌธ์์ด๋ก ์ง๋ ฌํํ์ฌ ์น๋ทฐ์ ์ ๋ฌํฉ๋๋ค.
sealed class BridgeResponse {
abstract fun toJson(): String
data class Success(val data: Any? = null) : BridgeResponse() {
override fun toJson(): String = JSONObject().apply {
put("success", true)
if (data != null) {
when (data) {
is JSONObject -> put("data", data)
is JSONArray -> put("data", data)
is List<*> -> put("data", JSONArray(data))
is Map<*, *> -> put("data", JSONObject(data))
else -> put("data", data)
}
}
}.toString()
}
data class Error(val code: BridgeErrorCode) : BridgeResponse() {
override fun toJson(): String = JSONObject().apply {
put("success", false)
put(
"error",
JSONObject().apply {
put("code", code.value)
},
)
}.toString()
}
}
4. ๊ฐ์ ํจ๊ณผ ์ ๋ฆฌ
๋ฒ์ ๋ถ๊ธฐ ๋ก์ง ์ ๊ฑฐ
์ฑ์ด ๋ฏธ์ง์ type์ ๋ํด ์๋ฌ๋ฅผ ๋ฐํํ ์ ์๊ฒ ๋๋ฉด์, ์น์ ์ฐ์ฌํ๋ checkAppVersion ๋ถ๊ธฐ ๋ก์ง์ ์ ๊ฑฐํ ์ ์์์ต๋๋ค. ๋งค ์
๋ฐ์ดํธ ๋ง๋ค ๋ฒ์ ์ ์ฒดํฌํ ํ์ ์์ด ๋ฏธ์ง์ ์ ์ผ๊ด๋ ์
๋ฐ์ดํธ ํ์
์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ก์ต๋๋ค.
๋๋ฒ๊น ์ฉ์ด์ฑ ํฅ์
๊ธฐ์กด์๋ ๋ฏธ์ง์ ์ปค๋งจ๋ ํธ์ถ ์ ์๋ฌด๋ฐ ์๋ต ์์ด ์คํจํ๊ธฐ ๋๋ฌธ์, ๋ฌธ์ ๊ฐ ๋ฐ์ํด๋ ์์ธ์ ํ์
ํ๊ธฐ ์ด๋ ค์ ์ต๋๋ค. ์ด์ ๋ ์ฑ์ด UNKNOWN_TYPE, UNSUPPORTED_TYPE, INVALID_PARAMS ๋ฑ ๊ตฌ์ฒด์ ์ธ ์๋ฌ ์ฝ๋๋ฅผ ๋ฐํํ๋ฏ๋ก, ์น์์ ์ด๋ค ์ด์ ๋ก ์คํจํ๋์ง ๋ฐ๋ก ํ์ธํ ์ ์์ด ๋๋ฒ๊น
์ด ํจ์ฌ ์์ํด์ก์ต๋๋ค.
์ปค๋งจ๋ ๋ณ๊ฒฝ์ ์ ์ฐํ ๊ตฌ์กฐ
๊ธฐ์กด์๋ ์ปค๋งจ๋์ ํ๋ผ๋ฏธํฐ๊ฐ ์ถ๊ฐ๋๊ฑฐ๋ ์ญ์ ๋ ๋, ์น์์ ๋ฒ์ ๋ณ๋ก ๋ค๋ฅธ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ณด๋ด๋ ๋ถ๊ธฐ๊ฐ ํ์ํ์ต๋๋ค. ์๋ฅผ ๋ค์ด ํน์ ์ปค๋งจ๋์ ์ ํ๋ผ๋ฏธํฐ๊ฐ ์ถ๊ฐ๋๋ฉด, ํด๋น ํ๋ผ๋ฏธํฐ๋ฅผ ๋ชจ๋ฅด๋ ๊ตฌ๋ฒ์ ์ฑ์ ์ํด ๋ฒ์ ๋ณ๋ก ๋ณด๋ด๋ JSON์ ๋ฌ๋ฆฌํด์ผ ํ์ต๋๋ค.
์ด์ ๋ ์น์์ ํญ์ ์ต์ ํ๋ผ๋ฏธํฐ๋ฅผ ํฌํจํ JSON์ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ์ฑ์ ํ์๊ฐ ํ์/์ ํ ํ๋๋ฅผ ๊ตฌ๋ถํ์ฌ ์ฒ๋ฆฌํ๋ฏ๋ก, ๊ตฌ๋ฒ์ ์ฑ์ ๋ชจ๋ฅด๋ ํ๋๋ฅผ ๋ฌด์ํ๊ณ ์ ๋ฒ์ ์ฑ์ ์ ํ๋๋ฅผ ํ์ฉํฉ๋๋ค. ์ ์ปค๋งจ๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ์๋ ๊ตฌ๋ฒ์ ์ฑ์ UNKNOWN_TYPE์ ๋ฐํํ๋ฏ๋ก, ์น์์ ๊ตฌ๋ฒ์ ์ฑ์ ๋ํ ๋ฒ์ ์ฒดํฌ๊ฐ ํ์ ์์ด์ก์ต๋๋ค.
๋ง๋ฌด๋ฆฌํ๋ฉฐ
์ด๋ฒ ์์ ์์๋ Android ๊ตฌํ๋ฟ๋ง ์๋๋ผ ์น ์ธก ์ค๊ณ์ ๊ตฌํ๋ ํจ๊ป ๋งก์์ต๋๋ค. ํ์์๋ ๋ค์ดํฐ๋ธ ๊ด์ ์์๋ง ๋ธ๋ฆฟ์ง๋ฅผ ๋ฐ๋ผ๋ดค๋๋ฐ, ์น์ ์ ์ฅ์ด ๋์ด๋ณด๋ ๊ธฐ์กด ๊ตฌ์กฐ์ ๋ถํธํจ์ด ๋ ์ค๊ฐ๋ฌ์ต๋๋ค. ๋ฒ์ ๋ถ๊ธฐ๋ฅผ ์ ์ด๋ ๊ฒ ๋ง์ด ๋ฃ์ ์๋ฐ์ ์์๋์ง, ์๋ฌ ์๋ต์ด ์๋ค๋ ๊ฒ ํธ์ถํ๋ ์ชฝ์์ ์ผ๋ง๋ ๋ต๋ตํ ์ผ์ธ์ง ์ง์ ๊ฒช์ด๋ณด๋ ํ ์๋ฟ์์ต๋๋ค.
์์ชฝ์ ๋ชจ๋ ๊ณ ๋ คํ ๋๋ถ์ โ์ฑ์ด ๋ชจ๋ฅด๋ ์ปค๋งจ๋์ ๋ํด ๋ช ํํ๊ฒ ์๋ฌ๋ฅผ ๋ฐํํ๋คโ๋ ์์น์ ์์ฐ์ค๋ฝ๊ฒ ์ธ์ธ ์ ์์๊ณ , ๊ทธ ์์น ํ๋๋ก ๋ฒ์ ๋ถ๊ธฐ ์ ๊ฑฐ, ๋ฐฐํฌ ์์ ์์กด์ฑ ํด์๊น์ง ์ด์ด์ง ์ ์์์ต๋๋ค. ์์ฒญ/์๋ต ํ์์ด ํต์ผ๋๊ณ ์๋ฌ ์ฒ๋ฆฌ๋ ๊ท๊ฒฉํ๋๋ฉด์, ์น-๋ค์ดํฐ๋ธ ๊ฐ ํ์ ๊ณผ์ ์์์ ์ปค๋ฎค๋์ผ์ด์ ๋น์ฉ์ด ์ค์ด๋๋ ๊ฒ ๋ํ ๊ธฐ๋ํ๊ณ ์์ต๋๋ค.