안드로이드(JavaScript)

안드로이드 환경에서 JavaScript만을 사용해 채널톡 SDK를 사용하기 위해서는 아래와 같은 설정이 필요합니다.

🚧

JavaScript SDK만 사용하는 경우, 푸시 알림 기능을 지원하지 않습니다.

푸시 알림 기능을 사용하려면 안드로이드(SDK)를 참고합니다.

새 탭 허용하기

안드로이드 웹뷰는 기본적으로 새 탭을 허용하지 않기 때문에, 이를 허용하기 위한 코드를 추가로 작성해야 합니다.

webChromeClient = object : WebChromeClient() {
    override fun onCreateWindow(
        view: WebView?,
        isDialog: Boolean,
        isUserGesture: Boolean,
        resultMsg: Message?
    ): Boolean {
        // intercept new window
        val transport = resultMsg?.obj as? WebView.WebViewTransport
        val newWebView = WebView(this@WebViewActivity)

        view?.addView(newWebView)
        transport?.webView = newWebView
        resultMsg?.sendToTarget()

        newWebView.webViewClient = object : WebViewClient() {
            // handle url navigating and create new activity
            override fun shouldOverrideUrlLoading(
                    view: WebView,
                    url: String
            ): Boolean {
                return when {
                    url.startsWith("tel:") -> {
                        val intent = Intent(Intent.ACTION_DIAL, Uri.parse(url))
                        startActivity(intent)
                        true
                    }
                    url.startsWith("mailto:") -> {
                        val intent = Intent(Intent.ACTION_SENDTO, Uri.parse(url))
                        startActivity(intent)
                        true
                    }
                    else -> {
                        Intent(view.context, WebViewActivity::class.java)
                            .apply { putExtra("url", url) }
                            .also { intent -> startActivity(intent) }
                        true
                    }
                }
            }
        }

        return true
    }
}

파일 업로드

안드로이드 웹뷰는 기본적으로 파일 선택 동작을 구현해놓지 않기 때문에, 이를 구현하는 코드를 추가로 작성해야 합니다.

📘

모바일 환경에서 조금 더 쉽고 직관적으로 채널톡 SDK를 활용하기를 원한다면 안드로이드 SDK를 사용할 것을 권장합니다.

  1. 웹뷰가 파일에 접근하는 것을 허용해준 후, 유저가 파일 선택기를 열고자 하는 순간을 감지할 수 있도록 WebChromeClient를 설정합니다.
yourWebViewInstance.apply {
    settings.apply {
        // -- your other WebView options --

        // add this line to make WebView to access file system
        allowFileAccess = true
    }

    webChromeClient = object : WebChromeClient() {
        override fun onShowFileChooser(
                webView: WebView,
                filePathCallback: ValueCallback<Array<Uri>>,
                fileChooserParams: FileChooserParams
        ): Boolean {
                // -- snip --
        }
}
  1. 아래 WebViewAttachmentModule 클래스를 복사해 사용합니다. WebViewAttachmentModule는 파일 선택기 콜백을 동작시킵니다.

🚧

onShowFileChooser는 true를 반환(return)해야 합니다.

WebChromeClientonShowFileChooserfalse를 반환하는 경우 filePathCallback이 호출되는 것을 허용하지 않습니다. getIntentChooser는 항상 filePathCallback을 호출하기 때문에 onShowFileChoosertrue를 반환할 때만 getIntentChooser를 호출해야 합니다.

class WebViewAttachmentModule {

    private var filePathCallback: ValueCallback<Array<Uri>>? = null

    fun getIntentChooser(filePathCallback: ValueCallback<Array<Uri>>, acceptTypes: Array<String>): Intent {
                // init filePathCallback
        this.filePathCallback?.onReceiveValue(null)
        this.filePathCallback = filePathCallback

        return Intent(Intent.ACTION_GET_CONTENT).apply {
            addCategory(Intent.CATEGORY_OPENABLE)
            type = "*/*"
            putExtra(Intent.EXTRA_MIME_TYPES, getMimeTypesFromAcceptTypes(acceptTypes))
        }
    }

        // handle onActivityResult process in activity
    fun handleResult(requestCode: Int, resultCode: Int, data: Intent?) {
        val uri = data?.data
        if (requestCode == WEB_VIEW_FILE_UPLOAD_REQUEST_CODE && resultCode == AppCompatActivity.RESULT_OK && uri != null) {
            filePathCallback?.onReceiveValue(arrayOf(uri))
        } else {
            filePathCallback?.onReceiveValue(null)
        }
        filePathCallback = null
    }

    private fun getMimeTypesFromAcceptTypes(acceptTypes: Array<String>): Array<String> {
        return (acceptTypes
                .mapNotNull { acceptType ->
                    when {
                        // Seperate case of extensions and MIME Type
                        acceptType.startsWith(".") -> MimeTypeMap.getSingleton().getMimeTypeFromExtension(acceptType.substring(1))
                        else -> acceptType
                    }
                }
                // setting "*/*" when acceptType is not exist
                .filter { it.isNotBlank() }
                .takeIf { it.isNotEmpty() }
                ?: listOf("*/*"))
                .toTypedArray()
    }
}

예시 코드

🚧

참고 코드이므로 상황에 맞게 변경해 사용하는 것을 추천합니다.

class WebViewActivity : AppCompatActivity() {
  
    private val attachmentModule = WebViewAttachmentModule()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web_view)

        webview.webViewClient = WebViewClient()

        val url = intent.getStringExtra("url") ?: SOME_DEFAULT_URL

        webview.apply {
            settings.apply {
                // 웹뷰 설정
                javaScriptEnabled = true
                domStorageEnabled = true
                useWideViewPort = true
                allowFileAccess = true

                setSupportMultipleWindows(true)
                javaScriptCanOpenWindowsAutomatically = true
            }

            webChromeClient = object : WebChromeClient() {
                // 파일 업로드 처리
                override fun onShowFileChooser(
                        webView: WebView,
                        filePathCallback: ValueCallback<Array<Uri>>,
                        fileChooserParams: FileChooserParams
                ): Boolean {
                    startActivityForResult(
                            attachmentModule.getIntentChooser(
                                    filePathCallback,
                                    fileChooserParams.acceptTypes,
                            ),
                            Const.WEB_VIEW_FILE_UPLOAD_REQUEST_CODE,
                    ) // 여기서 true를 반환할 때만 getIntentChooser()를 호출해야 합니다.
                    return true
                }
              
                override fun onCreateWindow(
                    view: WebView?,
                    isDialog: Boolean,
                    isUserGesture: Boolean,
                    resultMsg: Message?
                ): Boolean {
                    // 새로운 윈도우 관련 처리
                    val transport = resultMsg?.obj as? WebView.WebViewTransport
                    val newWebView = WebView(this@WebViewActivity)

                    view?.addView(newWebView)
                    transport?.webView = newWebView
                    resultMsg?.sendToTarget()

                    newWebView.webViewClient = object : WebViewClient() {
                        // URL 이동 관련 처리
                        override fun shouldOverrideUrlLoading(
                                view: WebView,
                                url: String
                        ): Boolean {
                            return when {
                                url.startsWith("tel:") -> {
                                    val intent = Intent(Intent.ACTION_DIAL, Uri.parse(url))
                                    startActivity(intent)
                                    true
                                }
                                url.startsWith("mailto:") -> {
                                    val intent = Intent(Intent.ACTION_SENDTO, Uri.parse(url))
                                    startActivity(intent)
                                    true
                                }
                                else -> {
                                    Intent(view.context, WebViewActivity::class.java)
                                        .apply { putExtra("url", url) }
                                        .also { intent -> startActivity(intent) }
                                    true
                                }
                            }
                        }
                    }

                    return true
                }
            }
        }

        webview.loadUrl(url)
    }
  
    // handle file upload
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        attachmentModule.handleResult(requestCode, resultCode, data)

        super.onActivityResult(requestCode, resultCode, data)
    }
}

안드로이드(SDK)

웹뷰 안에 JavaScript SDK를 내장시키는 방식을 사용하지 않고 안드로이드 SDK를 사용하는 경우 아래와 같은 방법을 사용할 수 있습니다. 안드로이드 SDK를 사용할 경우, 푸시 알림 기능을 함께 사용할 수 있습니다. 안드로이드 SDK 시작하기푸시 알림을 참고합니다.

❗️

채널톡 SDK의 UI는 하나만 표시해야 합니다.

이미 채널톡 JavaScript SDK와 Android SDK를 동시에 사용하고 있다면 JavaScript SDK Boot OptionhideChannelButtonOnBoothidePopup=true로 설정하여 채널톡 버튼과 팝업을 숨겨야 합니다. 안드로이드 SDK와 JavaScript SDK를 동시에 사용하는 경우에는 같은 UI가 화면에 중복 표시될 수 있습니다.

안드로이드 SDK가 현재 URL 정보를 알 수 있도록, setPage를 사용하기를 권장합니다. 안드로이드 SDK는 Page 정보를 통해 필요한 이벤트를 발생시키거나, 서포트봇을 동작시킵니다.

class WebViewActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
 
        ...

        webview.apply {
            settings.apply {
                // 옵션 설정
            }

            webViewClient = object : WebViewClient() {
                override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
                    // 현재 페이지를 SDK가 알 수 있도록 합니다
                    ChannelIO.setPage(url)
                }
            }
        }

        webview.loadUrl(url)
    }
}

iOS

이 단락에서는 iOS WebView 환경에서 모바일 웹에 설치된 JavaScript SDK를 사용하는 방법을 서술합니다.

🚧

JavaScript만 사용하는 경우, 푸시 알림 기능을 지원하지 않습니다.

푸시 알림 기능을 사용하려면 ChannelIO iOS SDK를 참고하세요.

아래 예시는 임의의 WebView ViewController에 url이 YOUR_WEB_URL 인 페이지를 로드하는 예제입니다.

import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
  
  var webView: WKWebView!
  var url: String = "YOUR_WEB_URL"
  override func loadView() {
    let config = WKWebViewConfiguration()
    config.allowsInlineMediaPlayback = true  // # 1. modify to true inlineMediaPlay option
    
    webView = WKWebView(frame: .zero, configuration: config)
    webView.uiDelegate = self
    view = webView  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    let myURL = URL(string:self.url)
    let myRequest = URLRequest(url: myURL!)
    webView.load(myRequest)
  }
  
  // #2. target blank handleing
  func webView(
    _ webView: WKWebView, 
    createWebViewWith configuration: WKWebViewConfiguration, 
    for navigationAction: WKNavigationAction, 
    windowFeatures: WKWindowFeatures) -> WKWebView? {
    if navigationAction.targetFrame == nil {
      webView.load(navigationAction.request)
    }
    return nil
  }
}

필요한 추가 처리

위에 기술한 코드 예시에 이미 각각 #1, #2 표시가 되어 있습니다.

# 1. allowsInlineMediaPlayback

iOS WKWebView의 allowsInlineMediaPlayback은 HTML5 video를 인라인으로 재생할 것인지, 전체화면으로 재생할 것인지에 대한 설정값입니다. default는 false입니다. 따라서 SDK 내부에 비디오가 있는 경우에 자동으로 전체화면으로 재생합니다.

이를 수정하기 위해 allowsInlineMediaPlayback 을 true 로 설정합니다.

# 2. target blank handling

채널톡 JavaScript SDK 링크의 속성은 모두 target="_blank"으로, 해당 링크를 새탭으로 열게 됩니다. 그러나, iOS의 WKWebView는 새 탭에 대한 처리가 되어있지 않아 이에 대한 처리가 필요합니다.

해당 처리는, target="_blank"에 대한 대한 액션인 경우 URL을 받아와, WebView에 URL을 로드하는 방식으로 해결합니다.