Lewati ke konten

Integrasi Android

Tampilkan Webchat di aplikasi Android lewat WebView, dengan data user yang login langsung terisi dan riwayat chat yang ikut pindah perangkat.

Webchat bisa dibuka layar penuh di dalam aplikasi Android lewat WebView. Aplikasi mengirim nama dan nomor HP user yang login, sehingga form pra-chat dilewati. Contoh di halaman ini memakai Kotlin.

URL chat

Ambil ID channel di Integrasi → Webchat → ikon edit → bagian Pasang. Buka URL berikut di WebView:

Terminal
https://app.csku.ai/webchat/<ID_CHANNEL>?embed=app
ParameterWajibKeterangan
embed=appYaMode aplikasi: layar penuh, langsung terbuka, menerima pesan dari aplikasi.
header=0TidakMenyembunyikan header chat (nama, logo, tombol riwayat, tombol tutup). Pakai jika aplikasi punya toolbar sendiri.
name, phone, emailTidakData user yang login, aturannya sama dengan identify. Jika name dan phone valid, form pra-chat dilewati. Data tidak valid diabaikan.
autostart=1TidakSesi chat langsung dibuat saat halaman dibuka, tidak menunggu pesan pertama. Event session langsung terkirim.

Parameter header, name, phone, email, dan autostart hanya berlaku bersama embed=app, dan nilainya harus persis seperti di tabel.

Cara tercepat: buka URL dengan data user, tanpa kode tambahan.

Kotlin
val url = Uri.parse("https://app.csku.ai/webchat/$CHANNEL_KEY").buildUpon()
    .appendQueryParameter("embed", "app")
    .appendQueryParameter("autostart", "1")
    .appendQueryParameter("name", user.name)
    .appendQueryParameter("phone", user.phone)
    .appendQueryParameter("email", user.email ?: "")
    .build()

webView.loadUrl(url.toString())

Data di URL tercatat

Parameter URL tersimpan di riwayat dan log server seperti URL lainnya. Token chat tidak bisa dikirim lewat URL; kirim lewat identify.

Setup WebView

Aktifkan JavaScript dan DOM storage (sesi chat disimpan di localStorage), daftarkan bridge bernama CskuWebchatBridge, lalu muat URL chat.

Kotlin
class SupportChatActivity : AppCompatActivity() {

    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        webView = WebView(this)
        setContentView(webView)

        webView.settings.apply {
            javaScriptEnabled = true
            domStorageEnabled = true
        }

        webView.addJavascriptInterface(ChatBridge(), "CskuWebchatBridge")
        webView.webViewClient = ChatWebViewClient()
        webView.webChromeClient = ChatChromeClient(this)

        webView.loadUrl("https://app.csku.ai/webchat/$CHANNEL_KEY?embed=app")
    }

    // Tombol back menutup chat, bukan halaman sebelumnya di WebView
    @Deprecated("Deprecated in Java")
    override fun onBackPressed() = finish()
}

Data user (identify)

Kirim data user setelah onPageFinished. Jika data tiba sebelum chat selesai dimuat, datanya tetap dipakai.

Kotlin
private inner class ChatWebViewClient : WebViewClient() {

    override fun onPageFinished(view: WebView, url: String) {
        val user = Session.currentUser ?: return

        val payload = JSONObject()
            .put("name", user.name)
            .put("phone", user.phone)
            .put("email", user.email ?: "")
        // Token chat dari server Anda (event session); lewati jika belum ada
        user.chatToken?.let { payload.put("token", it) }

        postToChat(view, "identify", payload)
    }

    // Link di dalam pesan dibuka di browser, bukan di WebView chat
    override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
        val url = request.url
        if (url.host == "app.csku.ai") return false
        startActivity(Intent(Intent.ACTION_VIEW, url))
        return true
    }
}

fun postToChat(view: WebView, type: String, payload: JSONObject? = null) {
    val msg = JSONObject().put("source", "csku-webchat").put("type", type)
    if (payload != null) msg.put("payload", payload)
    view.evaluateJavascript("window.postMessage($msg, '*')", null)
}
FieldWajibAturan
nameYa2–60 karakter.
phoneYa8–15 digit, boleh diawali +. Spasi, -, (, ), dan . dibuang dulu. Contoh: 081234567890, +6281234567890.
emailTidakFormat email valid, maksimal 120 karakter.
tokenTidakToken chat dari event session. 16–128 karakter: huruf, angka, _, -. Token salah format diabaikan.
  • Data valid: form pra-chat dilewati, dan tim CS melihat nama serta nomor ini di menu Chat.
  • Data tidak valid: diabaikan, user melihat form pra-chat seperti biasa.
  • Hanya identify valid pertama di setiap pemuatan halaman yang dipakai.
  • Tanpa token: jika perangkat ini sudah pernah chat, profil yang tersimpan tidak ditimpa.
  • Dengan token: chat di perangkat ini diganti dengan chat milik token tersebut, termasuk riwayatnya.

Pesan dari aplikasi ke chat

Semua dikirim lewat postToChat(webView, type) dengan format {"source":"csku-webchat","type":"...","payload":{...}}.

typePayloadFungsi
identify{ name, phone, email, token }Mengisi data user.
open-Menandai chat sedang dilihat. Jumlah belum dibaca direset ke 0.
close-Menandai chat tidak sedang dilihat. Pesan baru dihitung belum dibaca.
history-Membuka daftar riwayat percakapan. Hanya berlaku jika user punya lebih dari satu percakapan. Pakai dari toolbar aplikasi saat header=0.

WebView di background

Jika WebView chat disimpan di background (misalnya di bottom sheet), kirim close saat disembunyikan dan open saat ditampilkan lagi, supaya badge belum dibaca akurat.

Event dari chat ke aplikasi

Chat memanggil CskuWebchatBridge.postMessage(json) dengan string JSON {"source":"csku-webchat","type":"...","payload":{...}}.

Kotlin
private inner class ChatBridge {
    @JavascriptInterface
    fun postMessage(json: String) {
        val msg = runCatching { JSONObject(json) }.getOrNull() ?: return
        if (msg.optString("source") != "csku-webchat") return

        runOnUiThread {
            when (msg.optString("type")) {
                "ready" -> { /* halaman chat siap */ }
                "unread" -> updateBadge(msg.optJSONObject("payload")?.optInt("count") ?: 0)
                "close" -> finish() // user menekan tombol tutup di header chat
                "session" -> msg.optJSONObject("payload")?.optString("token")
                    ?.takeIf { it.isNotEmpty() }
                    ?.let { saveChatTokenToServer(it) }
            }
        }
    }
}
typePayloadKapan dikirim
ready-Halaman chat sudah dimuat.
unread{ count } (0–99)Jumlah belum dibaca berubah. Bertambah hanya saat chat berstatus close.
close-User menekan tombol tutup di header, atau Escape di keyboard fisik. Aplikasi yang memutuskan apa yang ditutup.
session{ token }Setiap kali sesi chat dibuka atau dibuat. Hanya dikirim di mode aplikasi.

Selain session, event tidak pernah membawa data user atau isi pesan. Tanpa bridge, chat tetap berjalan, tetapi tombol tutup di header tidak berfungsi. Daftarkan bridge atau pakai header=0.

Token sesi

Token dari event session mengikat percakapan ke user. Simpan di server Anda untuk user yang login, lalu kirim lagi lewat identify.token. Dengan begitu riwayat chat user tetap muncul setelah instal ulang atau di HP lain.

  1. Terima event session, kirim token ke server Anda bersama ID user yang login.
  2. Saat user membuka chat, ambil token user dari server Anda.
  3. Kirim token di field token pada identify.

Tanpa token, user yang sama di HP lain atau setelah instal ulang tercatat sebagai pengunjung baru.

Perlakukan token seperti password

Siapa pun yang memegang token bisa membaca dan mengirim pesan di percakapan user tersebut. Simpan hanya di server Anda, berikan hanya ke user yang login sebagai pemiliknya, dan jangan pernah menulisnya ke log.

Kirim gambar dan PDF

User bisa mengirim JPG, PNG, WEBP, GIF, dan PDF hingga 5 MB. WebView butuh WebChromeClient yang membuka pemilih file:

Kotlin
class ChatChromeClient(private val activity: ComponentActivity) : WebChromeClient() {

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

    private val picker = activity.registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        callback?.onReceiveValue(FileChooserParams.parseResult(result.resultCode, result.data))
        callback = null
    }

    override fun onShowFileChooser(
        view: WebView, filePathCallback: ValueCallback<Array<Uri>>, params: FileChooserParams
    ): Boolean {
        callback?.onReceiveValue(null)
        callback = filePathCallback
        picker.launch(params.createIntent())
        return true
    }
}

Buat ChatChromeClient di onCreate, sebelum Activity STARTED, karena memakai registerForActivityResult.

Logout dan ganti akun

Sesi chat disimpan di penyimpanan WebView dan berlaku untuk seluruh aplikasi. Jika user A logout lalu user B login di HP yang sama tanpa membersihkan penyimpanan, B melihat percakapan A. Saat logout, panggil:

Kotlin
WebStorage.getInstance().deleteAllData()
CookieManager.getInstance().removeAllCookies(null)

Jika WebView juga dipakai untuk halaman lain yang datanya tidak boleh ikut terhapus, jalankan chat di proses terpisah dengan WebView.setDataDirectorySuffix("csku-chat") (Android 9+), lalu hapus direktori data itu saat logout.

Layar penuh (edge-to-edge)

Halaman chat otomatis memberi jarak untuk status bar, navigation bar, dan notch. Beberapa versi WebView lama tidak meneruskan jarak ini. Jika header tertutup status bar atau kotak ketik tertutup navigation bar (Android 15 / targetSdk 35 memaksa edge-to-edge), beri padding di native:

Kotlin
ViewCompat.setOnApplyWindowInsetsListener(webView) { v, insets ->
    val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime())
    v.setPadding(bars.left, bars.top, bars.right, bars.bottom)
    WindowInsetsCompat.CONSUMED
}

Padding ini tidak membuat jarak dobel. Pakai android:windowSoftInputMode="adjustResize" pada Activity supaya kotak ketik tetap terlihat saat keyboard muncul.

Keamanan dan batasan

Identitas tidak diverifikasi

Nama dan nomor dari aplikasi tidak diverifikasi oleh CSKU. Siapa pun yang membuka URL chat bisa mengirim nomor lain. Gunakan sebagai info bantu untuk tim CS, bukan bukti identitas. Jangan membuka data sensitif (saldo, pesanan, data akun) hanya berdasarkan nomor yang tampil di chat.

  • Pengunjung baru melewati pemeriksaan anti-spam otomatis saat memulai chat. WebView harus bisa memuat challenges.cloudflare.com.
  • Satu pesan maksimal 2000 karakter.
  • Tampilan chat berbahasa Indonesia dan bertema terang.

Checklist uji

  1. Login, buka chat: form pra-chat tidak muncul. Kirim pesan, cek nama dan nomor di menu Chat dashboard.
  2. Nomor salah format (misalnya 12345): form pra-chat muncul.
  3. Tutup chat lalu buka lagi: riwayat pesan masih ada.
  4. Kirim close, lalu balas dari dashboard: event unread masuk dengan count bertambah.
  5. Tombol tutup di header menutup Activity, atau header=0 dan toolbar aplikasi tampil.
  6. Kirim gambar dan PDF.
  7. Ketuk link di pesan: terbuka di browser.
  8. Logout lalu login akun lain: chat mulai kosong.
  9. Event session masuk dan token tersimpan di server. Login akun yang sama di HP lain: riwayat chat muncul.
  10. HP dengan gesture navigation dan notch: header dan kotak ketik tidak tertutup.

Troubleshooting

GejalaSolusi
Form pra-chat tetap munculPastikan URL memakai ?embed=app. Cek nama minimal 2 karakter dan nomor 8–15 digit. Kirim identify di onPageFinished.
Nama lama tampil setelah ganti akunHapus penyimpanan WebView saat logout.
Tombol tutup tidak berfungsiDaftarkan CskuWebchatBridge atau pakai header=0.
Tombol lampiran tidak membuka galeriImplementasikan onShowFileChooser.
Muncul "Verifikasi keamanan gagal"Izinkan challenges.cloudflare.com di jaringan atau network security config.
Halaman kosongAktifkan JavaScript dan DOM storage. Cek ID channel di dashboard.