Jetlink
Mobil SDK Webview
Entegrasyon
Kılavuzu
Bu
doküman, Jetlink Mobil
SDK'sının native iOS ve
Android uygulamalarına
entegrasyonu sırasında
yapılması gereken
yapılandırmaları
açıklamaktadır.
1.
Genel
Bakış
Jetlink
Mobil SDK, native uygulama
içerisinde WKWebView (iOS) veya WebView (Android) bileşeni
üzerinden çalışan bir web
arayüzü olarak
yüklenmektedir. Sohbet
arayüzü, sesli görüşme,
dosya yükleme ve mikrofon
erişimi gibi özellikler bu
webview içerisinde
çalıştığından, native
tarafta belirli
yapılandırmaların doğru
biçimde tamamlanması
zorunludur. Söz konusu
yapılandırmalar
tamamlanmadığı takdirde,
ilgili özellikler son
kullanıcıya sunulamayacak
veya beklenen biçimde
çalışmayacaktır.
Bu
dokümanda; webview
üzerinden native tarafa
iletilen olaylar, native
tarafta yapılması gereken
izin ve davranış
yapılandırmaları ile sıkça
karşılaşılan entegrasyon
sorunlarına yönelik
çözümler yer
almaktadır.
Uygulamanızda uygun bir chatbot ikonu
kullanın. Bu ikon; menüde, üst bilgi
(header) alanında veya kullanıcıların
chatbot arayüzüne kolayca erişebileceği
herhangi bir noktada
konumlandırılabilir.
Jetlink
WebView URL bilgisine Jetlink Dashboard
üzerinden ulaşabilirsiniz:
Settings
→ iOS
Jetlink
WebView URL formatı aşağıdaki
gibidir:
https://public.jetlink.io/Home/MobilSDK?appId=<YOUR-APP-ID>&appKey=<YOUR-APP-KEY>
&mobileSDKType=ios&mobileDeviceName=<DEVICE-NAME>&mobileDeviceOS=<DEVICE-OS-VERSION>Jetlink
WebView URL'ini açarken kullanıcıya ait
bilgileri de iletebilirsiniz.
Örneğin:
- Ad
- Soyad
- E-posta adresi
- Telefon numarası
- Sisteminizdeki benzersiz kullanıcı
ID'si
Örnek
URL:
https://public.jetlink.io/Home/MobilSDK?appId=<YOUR-APP-ID>&appKey=<YOUR-APP-KEY>
&mobileSDKType=ios&mobileDeviceName=<DEVICE-NAME>&mobileDeviceOS=<DEVICE-OS-VERSION>
&username=<USER-FIRST-NAME>&userSurname=<USER-SURNAME>
&userEmail=<USER-EMAIL>&userPhone=<USER-PHONE-NUMBER>
&userSourceUserId=<USER-UNIQUE-ID-IN-YOUR-SYSTEM>Jetlink
WebView URL'i üzerinden sisteminizde
bulunan
kullanıcı bilgilerini iletebilirsiniz.
Örneğin:
- Ad
- Soyad
- E-posta
- Telefon
numarası
- Benzersiz kullanıcı
ID'si
Bu
bilgilerin
tamamını veya yalnızca ihtiyaç duyduğunuz
kısmını gönderebilirsiniz. Jetlink arayüzü
bu
bilgilerle açılır ve kullanıcı daha
sonraki
görüşmelerinde aynı bilgiler üzerinden
tanınarak sohbet geçmişiyle
ilişkilendirilebilir.
Hepsi bu kadar. Kullanıcılarınız artık
mobil
uygulamanız içerisinden Jetlink chatbot
ile
mesajlaşmaya başlayabilirler. 🚀
2.
Web Tarafından Native
Tarafa İletilen
Olaylar
Webview
içerisinde çalışan SDK,
belirli durumlarda native
uygulamaya window.postMessage arayüzü üzerinden olaylar
iletmektedir. Native
uygulamanın bu olayları
dinleyerek uygun
aksiyonları
gerçekleştirmesi
gerekmektedir.
2.1.
OPEN_APP_SETTINGS
Olayı
Tetiklenme
Koşulu: Kullanıcı sesli
görüşme başlatmak
istediğinde,
mikrofon erişimi daha önce
reddedilmişse SDK,
"Ayarlara
Git" ve "Vazgeç"
seçeneklerini
içeren bir uyarı
görüntüler.
Kullanıcı "Ayarlara Git"
seçeneğini tercih
ettiğinde bu
olay native tarafa
iletilir.
Bu olay, mikrofon izni
reddedilmiş olduğu sürece
kullanıcının sesli görüşme
ekranına her girişinde
yeniden
tetiklenebilmektedir.
Native
tarafın bu olayı aynı
oturum
içerisinde birden fazla
kez
alacak biçimde
yapılandırılmış
olması
gerekmektedir.
Olay
Yapısı:
{
"type":
"OPEN_APP_SETTINGS",
"source":
"jetlink-bot"
}
Beklenen
Native Aksiyon:
Uygulamanın
izin yönetimi ekranının
cihaz
sistem ayarlarında
açılması.
iOS
Tarafı
(WKWebView)
WKScriptMessageHandler arayüzü üzerinden olayın
dinlenmesi
gerekmektedir.
let
userContentController =
WKUserContentController()
userContentController.add(self,
name:
"OPEN_APP_SETTINGS")
let
config =
WKWebViewConfiguration()
config.userContentController
=
userContentController
func
userContentController(_
userContentController:
WKUserContentController,
didReceive
message: WKScriptMessage)
{
if
message.name ==
"OPEN_APP_SETTINGS"
{
if
let url = URL(string:
UIApplication.openSettingsURLString)
{
UIApplication.shared.open(url)
}
}
}
Handler
kaydı
(userContentController.add), WKWebView instance'ının her
oluşturulmasında
yapılandırma
aşamasında
gerçekleştirilmelidir.
WebView
yeniden oluşturulduğunda
handler'ın yeniden
kaydedilmemesi durumunda
olay
native tarafa
iletilemez.
webView.uiDelegate
=
self atamasının yapılmış olması
gerekmektedir.
Android
Tarafı
(WebView)
JavascriptInterface üzerinden bir köprü
tanımlanması
gerekmektedir.
class
JetlinkAndroidBridge(private
val activity: Activity)
{
@JavascriptInterface
fun
openAppSettings()
{
val
intent =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply
{
data
= Uri.fromParts("package",
activity.packageName,
null)
}
activity.startActivity(intent)
}
}
webView.addJavascriptInterface(JetlinkAndroidBridge(this),
"JetlinkAndroidBridge")
SDK, window.JetlinkAndroidBridge.openAppSettings() ve window.postMessage kanallarını birbirinden
bağımsız olarak eş zamanlı
biçimde çalıştırmaktadır.
Her
iki kanalın da native
tarafta
desteklenmesi tavsiye
edilmektedir.
3.
Mikrofon Erişimi
Yapılandırması
3.1.
iOS
Info.plist dosyasına aşağıdaki
anahtarın
eklenmesi
zorunludur.
<key>NSMicrophoneUsageDescription</key>
<string>Sesli
sohbet özelliğini
kullanabilmek için
mikrofon
erişimi
gereklidir.</string>
iOS
15
ve üzeri sürümlerde, WKWebView üzerinden gelen medya
erişim
taleplerinin
onaylanabilmesi
için WKUIDelegate üzerinde aşağıdaki metodun
uygulanması
gerekmektedir.
extension
ViewController:
WKUIDelegate
{
func
webView(_ webView:
WKWebView,
requestMediaCapturePermissionFor
origin:
WKSecurityOrigin,
initiatedByFrame
frame:
WKFrameInfo,
type:
WKMediaCaptureType,
decisionHandler:
@escaping
(WKPermissionDecision)
->
Void)
{
decisionHandler(.grant)
}
}
webView.uiDelegate
=
self atamasının yapılmış olması
gerekmektedir.
3.2.
Android
AndroidManifest.xml dosyasına aşağıdaki
izinlerin
eklenmesi
gerekmektedir.
<uses-permission
android:name="android.permission.RECORD_AUDIO"
/>
<uses-permission
android:name="android.permission.MODIFY_AUDIO_SETTINGS"
/>
<uses-permission
android:name="android.permission.INTERNET"
/>
WebView
içerisinden gelen getUserMedia taleplerinin
karşılanabilmesi
için WebChromeClient.onPermissionRequest metodunun aşağıdaki
biçimde
override edilmesi
gerekmektedir.
webView.webChromeClient
= object :
WebChromeClient()
{
override
fun
onPermissionRequest(request:
PermissionRequest)
{
activity.runOnUiThread
{
request.grant(request.resources)
}
}
}
Android
6.0 ve üzeri sürümlerde
runtime izin akışının
native
tarafta da yürütülmesi
gerekmektedir.
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.RECORD_AUDIO),
REQUEST_CODE_AUDIO
)
4.
Mikrofon İzin Akışı ve
Permission State
Senkronizasyonu
Sesli
görüşme özelliğinin
WebView
içerisinde tutarlı
çalışabilmesi için native
uygulamanın app-level
mikrofon
izin durumunu WebView
medya
izin akışına doğru şekilde
yansıtması gerekmektedir.
Web
tarafı getUserMedia({
audio: true
}) çağrısı yapar; bu çağrının
gerçek grant/deny kararı
Android ve iOS native
WebView
katmanında
verilmelidir.
4.1.
Beklenen Kullanıcı
Akışı
Kullanıcı sesli konuşma ikonuna tıkladığımda mikrofon izin kontrolü yapılmalıdır.
Kullanıcı daha önce mikrofon izni vermediyse native mikrofon izin popup gösterilmelidir.
Kullanıcı native popup üzerinde izin verirse kullanıcı doğrudan sesli konuşma ekranına yönlendirilmelidir.
Kullanıcı native popup üzerinde izin vermezse aynı kullanıcı aksiyonu içerisinde veya hemen sonrasında custom popup gösterilmemelidir.
Native popup reddedildikte sonra kullanıcımevcutsayfada kalmalıdır.
Kullanıcı daha sonra yenidensesli konuşma ikonunatıklarsa nativepopupaçılabiliyorsaaçılmalı,açılamıyorsacustom permission popupgösterilmelidir.
Custom popup üzerinden kullanıcı"Ayarlara Git" aksiyonu ile cihaz ayarlarına yönlendirilmelidir.
Kullanıcı daha önce mikrofon iznini vermişse herhangi bir popup tekrar gösterilmeden kullanıcı doğrudansesli konuşma ekranına yönlendirilmelidir.
Aynı kullanıcı aksiyonu içerisinde üst üste birden fazla permission popup gösterilmemelidir.
4.2.
Native Tarafından
Web'e
Permission State
Bildirimi
WebView
yeniden oluşturulduğunda,
navigation tamamlandığında
veya kullanıcı cihaz
ayarlarından uygulamaya
geri
döndüğünde native taraf
güncel
mikrofon izin durumunu
WebView'e bildirmelidir.
Bu
bildirim web tarafındaki
custom popup kararını
günceller.
Desteklenen
event isimleri aşağıdaki
gibidir:
micPermissionStateChanged: Genel state eventidir. detail.state değeri granted, denied veya prompt olmalıdır.
micPermissionGranted: App-level mikrofon izni verilmiştir.
micPermissionDenied: App-level mikrofon izni reddedilmiş veya cihaz ayarlarındankapatılmıştır.
micPermissionUndetermined:Kullanıcı henüz native izin kararı vermemiştir; native prompt gösterilebilir.
window.dispatchEvent(new
CustomEvent("micPermissionStateChanged",
{
detail:
{ state: "granted" } //
granted | denied |
prompt
}));
window.dispatchEvent(new
Event("micPermissionGranted"));
window.dispatchEvent(new
Event("micPermissionDenied"));
window.dispatchEvent(new
Event("micPermissionUndetermined"));
Alternatif
olarak window.postMessage payload'i de
kullanılabilir.
window.postMessage({
type:
"micPermissionStateChanged",
state:
"granted" // granted |
denied
|
prompt
},
"*");
Bu
bildirim custom popup
state'ini günceller. Ancak
gerçek medya izni, getUserMedia çağrısı sırasında
Android'de WebChromeClient.onPermissionRequest,
iOS'ta WKUIDelegate.requestMediaCapturePermissionFor tarafından app-level
permission state'e göre
grant/deny
edilmelidir.
4.3.
Android Settings
Dönüşü
Kullanıcı
custom popup üzerinden
uygulama ayarlarına
yönlendirildikten sonra
geri
döndüğünde Android
tarafında onResume içinde RECORD_AUDIO izni
tekrar okunmalı ve
WebView'e
state
bildirilmelidir.
override
fun onResume()
{
super.onResume()
notifyMicPermissionStateToWebView()
}
private
fun
notifyMicPermissionStateToWebView()
{
val
state = if
(
ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO)
==
PackageManager.PERMISSION_GRANTED
)
{
"granted"
}
else
{
"denied"
}
val
eventName = when (state)
{
"granted"
->
"micPermissionGranted"
"denied"
->
"micPermissionDenied"
else
->
"micPermissionUndetermined"
}
val
script =
"""
window.dispatchEvent(new
CustomEvent('micPermissionStateChanged',
{
detail:
{ state: '$state'
}
}));
window.dispatchEvent(new
Event('$eventName'));
""".trimIndent()
webView.post
{
webView.evaluateJavascript(script,
null)
}
}
4.4.
iOS Settings
Dönüşü
Kullanıcı
cihaz ayarlarından geri
döndüğünde iOS tarafında
app-level mikrofon izni
tekrar
okunmalı; WebView yeniden
oluşturulduysa navigation
tamamlandıktan sonra,
aktif
WebView varsa doğrudan
JavaScript event dispatch
edilmelidir.
func
notifyMicPermissionStateToWebView()
{
let
state:
String
let
eventName:
String
switch
AVAudioSession.sharedInstance().recordPermission
{
case
.granted:
state
=
"granted"
eventName
=
"micPermissionGranted"
case
.denied:
state
=
"denied"
eventName
=
"micPermissionDenied"
case
.undetermined:
state
=
"prompt"
eventName
=
"micPermissionUndetermined"
@unknown
default:
state
=
"denied"
eventName
=
"micPermissionDenied"
}
let
script =
"""
window.dispatchEvent(new
CustomEvent('micPermissionStateChanged',
{
detail:
{ state: '\(state)'
}
}));
window.dispatchEvent(new
Event('\(eventName)'));
"""
webView.evaluateJavaScript(script,
completionHandler:
nil)
}
4.5.
iOS Media Capture
Kararı
iOS
15
ve üzeri sürümlerde
WKWebView
medya erişim isteği
geldiğinde
native taraf app-level
mikrofon izin durumunu
okuyarak karar vermelidir.
App-level mikrofon izni granted ise
WebView yeniden
oluşturulmuş
olsa bile tekrar native
izin
popup'ı gösterilmeden .grant dönülmelidir.
@available(iOS
15.0,
*)
func
webView(_ webView:
WKWebView,
requestMediaCapturePermissionFor
origin:
WKSecurityOrigin,
initiatedByFrame
frame:
WKFrameInfo,
type:
WKMediaCaptureType,
decisionHandler:
@escaping
(WKPermissionDecision)
->
Void)
{
guard
type == .microphone ||
type ==
.cameraAndMicrophone else
{
decisionHandler(.deny)
return
}
switch
AVAudioSession.sharedInstance().recordPermission
{
case
.granted:
decisionHandler(.grant)
case
.denied:
decisionHandler(.deny)
case
.undetermined:
decisionHandler(.prompt)
@unknown
default:
decisionHandler(.deny)
}
}
5.
Dosya Eki ve Dosya
Seçici
Yapılandırması
Sohbet
arayüzündeki dosya ekleme
(attachment) butonuna
basıldığında native dosya
seçicinin açılması
beklenmektedir. Bu
davranış
webview içerisinde <input
type="file"> etkileşimi ile
yönetilmekte
olup, native tarafın
ilgili
olayları desteklemesi
gerekmektedir.
5.1.
Android
WebChromeClient.onShowFileChooser metodu
override edilmediği
takdirde
dosya seçici penceresi
açılmamaktadır. Bu konu
Android entegrasyonlarında
en
sık karşılaşılan
eksikliktir.
private
var fileChooserCallback:
ValueCallback<Array<Uri>>?
=
null
private
val
FILE_CHOOSER_REQUEST_CODE
=
1001
webView.webChromeClient
= object :
WebChromeClient()
{
override
fun
onShowFileChooser(
webView:
WebView,
filePathCallback:
ValueCallback<Array<Uri>>,
fileChooserParams:
FileChooserParams
):
Boolean
{
fileChooserCallback?.onReceiveValue(null)
fileChooserCallback
=
filePathCallback
val
intent =
fileChooserParams.createIntent()
try
{
startActivityForResult(intent,
FILE_CHOOSER_REQUEST_CODE)
}
catch (e:
ActivityNotFoundException)
{
fileChooserCallback
=
null
return
false
}
return
true
}
override
fun
onPermissionRequest(request:
PermissionRequest)
{
activity.runOnUiThread
{
request.grant(request.resources)
}
}
}
onActivityResult metodu
üzerinden seçim sonucunun
geri
iletilmesi
gerekmektedir.
override
fun
onActivityResult(requestCode:
Int, resultCode: Int,
data:
Intent?)
{
super.onActivityResult(requestCode,
resultCode,
data)
if
(requestCode ==
FILE_CHOOSER_REQUEST_CODE)
{
val
results =
WebChromeClient.FileChooserParams.parseResult(resultCode,
data)
fileChooserCallback?.onReceiveValue(results)
fileChooserCallback
=
null
}
}
Galeri
ve kamera erişimi için
aşağıdaki izinlerin
tanımlanması
gerekmektedir.
<uses-permission
android:name="android.permission.READ_MEDIA_IMAGES"
/>
<uses-permission
android:name="android.permission.READ_MEDIA_VIDEO"
/>
<uses-permission
android:name="android.permission.CAMERA"
/>
5.2.
iOS
WKWebView dosya
seçici davranışını
varsayılan
olarak desteklemektedir.
Yalnızca Info.plist dosyasında aşağıdaki
kullanım
açıklamalarının tanımlı
olması
gerekmektedir.
<key>NSPhotoLibraryUsageDescription</key>
<string>Sohbet
üzerinden dosya
gönderebilmek
için fotoğraf erişimine
izin
verin.</string>
<key>NSCameraUsageDescription</key>
<string>Sohbet
üzerinden fotoğraf
çekebilmek
için kameraya erişim
gereklidir.</string>
6.
Webview
Yerleşimi
6.1.
iOS
WKWebView
webView.scrollView.contentInsetAdjustmentBehavior
=
.never
webView.scrollView.bounces
=
false
webView.scrollView.alwaysBounceVertical
=
false
webView.scrollView.alwaysBounceHorizontal
=
false
WebView
constraint tanımlarının view.safeAreaLayoutGuide.bottomAnchor yerine view.bottomAnchor referansı ile yapılması
gerekmektedir.
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo:
view.topAnchor),
webView.leadingAnchor.constraint(equalTo:
view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo:
view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo:
view.bottomAnchor)
])
View
controller seviyesinde
aşağıdaki yapılandırmalar
tanımlanmalıdır.
edgesForExtendedLayout
=
.all
extendedLayoutIncludesOpaqueBars
=
true
7.
Sesli Görüşme - Arka
Plan
Davranışı
Kullanıcının
uygulamayı arka plana
alması
veya cihaz ekranını
kilitlemesi durumunda,
aktif
sesli görüşme SDK
tarafından
otomatik olarak
sonlandırılmaktadır. Söz
konusu davranış visibilitychange, pagehide ve blur olayları üzerinden
yönetilmektedir. Native
tarafta ek bir aksiyon
alınmasına gerek
bulunmamakla
birlikte, iOS için ses
oturumu
yapılandırmasının
aşağıdaki
biçimde tanımlanması
tavsiye
edilmektedir.
try?
AVAudioSession.sharedInstance().setCategory(
.playAndRecord,
mode:
.voiceChat,
options:
[.defaultToSpeaker,
.allowBluetooth]
)
try?
AVAudioSession.sharedInstance().setActive(true)
8.
Doğrulama
Listesi
Entegrasyon
tamamlandıktan sonra
aşağıdaki
senaryoların her birinin
doğrulanması
gerekmektedir.
Sohbet arayüzü açılmakta ve mesaj gönderilebilmektedir.
Sesli görüşme başlatıldığında native mikrofon izin diyaloğu görüntülenmektedir.
Mikrofon izni reddedildiğinde aynı kullanıcı aksiyonu içerisinde custom popup gösterilmemektedir.
Mikrofon izni reddedildikten sonraki denemelerde custom popup görüntülenmekte ve "Ayarlara Git" seçeneği sistem ayarları sayfasını açmaktadır.
Settings ekranında mikrofon izni verilip uygulamaya dönüldüğünde native taraf WebView'e granted state bildirmektedir.
App-level mikrofon izni verilmişse WebView yeniden oluşturulduğunda native permission popup tekrar gösterilmeden sesli görüşme başlatılabilmektedir.
Settings ekranında mikrofon izni kapatılıp uygulamaya dönüldüğünde native taraf WebView'e denied state bildirmektedir.
Dosya ekleme butonu native dosya seçici penceresini açmaktadır.
Sesli görüşme sırasında uygulamanın arka plana alınması durumunda görüşme sonlandırılmaktadır.
Cihaz dilinin değiştirilmesi durumunda arayüz dili buna uyum sağlamaktadır.