16200

一洽客服系统接入APP渠道

目录

1. 使用场景
2. webview打开窗口优势
3. 接口列表
4. APP接入注意事项
5. 示例代码

1.使用场景

当您在app中使用webview的方式打开一洽的对话窗口时,可使用此交互API做特定的业务逻辑处理

2.webview打开窗口优势

app接入客服咨询有两种途径,一种是使用一洽提供的SDK将其整合到自己的APP中用native的方式打开对话,一种是通过webview直接将一洽的对话窗口地址打开接入对话。
使用SDK接入对话需要考虑多种机型的适配和测试,工作量繁重,更新复杂不能及时使用一洽新的创新
使用webview的形式打开接入相对于SDK来说简单快速,适配问题不用考虑,同时又能在APP不更新的情况下使用最新的一洽功能,在遇到问题后不影响已上线的APP。

3.接口列表

3.1 APP注入对象
使用交互API需要app提供一洽调用的对象注入给webview。
提供对象:EchatJsBridge
提供方法:callEchatNative
方法定义:EchatJsBridge.callEchatNative(functionInfo)
参数定义:functionInfo为一个json字符串,json字符串包含的key列表如下:

属性名 类型 说明
functionName String 调用的方法名称(事件名称)
value String 不同的方法(事件)值的类型不一致,具体见详细的事件定义列表

示例:

EchatJsBridge.callEchatNative(JSON.stringify({"functionName": "newMsg",value:"你好,请问有什么可以帮您?"}));

此对象是由NavtiveApp通过WebView的方法创建交给JavaScript环境。当访客端的事件触发时会主动调用下面列表的中的事件。

功能 事件名称 事件值类型 事件值说明
访客对话状态变更 chatStatus String 对话状态事件
waiting:等待接入对话
chatting:对话中
robot:机器人对话中
leaveDisabled:客服离线、留言禁用
leaveToService:客服离线进入留言
leaveToUrl:客服离线跳转至url
end-[0-8]-0/1:对话结束
0-8含义为结束原因:
0:对话结束原因未知
1:访客结束对话
2:客服结束对话
5:客服退出结束对话
6:系统关闭对话
8:访客长时间为响应结束对话
0/1含义为是否需要评价
0:不需要评价
1:需要评价
接入对话的客服信息 chatStaffInfo String 对话的客服信息,json字符串 属性描述如下:
staffId:客服ID
staffNickName:客服的对外昵称
staffPhone:客服的对外电话
staffHead:客服的对外头像
staffInfo:客服的对外信息介绍
访客排队位置 queuePostion int 当前访客的排队位置,如果没有排队则不会触发此方法调用
收到新客服消息 newMsg String 访客收到的新客服消息text/[file]/[image]
text:收到的文本消息,消息中会有标签代表表情可直接拼接路径后获取表情图标地址如:表情的基础路径为http[s]://es.echatsoft.com
[file]:收到了新的文件
[image]:收到了新的图片
收到新系统消息 newSysMsg String 访客收到的新的系统消息text/[file]/[image]
text:收到的文本消息,消息中会有标签代表表情可直接拼接路径后获取表情图标地址如:表情的基础路径为http[s]://es.echatsoft.com
[file]:收到了新的文件
[image]:收到了新的图片
访客评价反馈 visitorEvaluate String 客服的评价结果 0/1-1/2
0/1含义为是否提交了评价
0:访客取消评价
1:访客提交了评价
1/2含义为提交评价时的对话状态
1:访客对话中
2:对话已结束
通知APP隐藏对话窗口 visitorHide 如果打开的web对话样式有标题栏,且支持访客最小化对话窗口,那么在访客点击最小化是会调用此方法
接收当前对话的公司ID sendCompnayId int 当前对话的账号ID。调用callEchatJs的getCompanyId方法后会触发此方法调用。
接收当前访客的访客ID sendVisitorId jsonString 在H5获取到当前访客的ID后发送给APP。详见下方访客ID json字段解释。
预览图片 previewImage jsonString 将当前对话中的图片列表发送给app进行预览。详见下方预览图片json字段解释。调用callEchatJs的setMediaPlayer方法接管图片预览后会触发此方法调用。
视频播放 video jsonString 将选择的视频信息送给app进行播放。详见下方视频播放json字段解释。调用callEchatJs的setMediaPlayer方法接管视频播放后会触发此方法调用。
打开链接(已废弃) openLink String 将要打开的链接通知app打开。调用callEchatJs的setLinkOpener方法接管链接打开后会触发此方法调用。
打开链接 openLinkV2 jsonString 将要打开的链接通知app打开。调用callEchatJs的setLinkOpener方法接管链接打开后会触发此方法调用 详见下方链接打开json字段解释。
接管访客发送消息 visitorPreSendMsg String 访客准备发送的消息内容。如果app设置了接管访客发送消息,则访客要发送的文本消息在发送时会提交给APP做处理,APP处理完成后调用callEchatJs的visitorSendMsg来通知H5将消息发送出去。
设置接管访客消息的方法:
加载对话窗口地址时追加fvMsg参数来他通知H5是否接管消息
1:接管发送消息 2:接管接收消息 3:接收发送和接收消息
接管访客接收消息 visitorPreReceiveMsg String 访客接收到的文本消息内容(可能包含html标签)。如果APP设置了接管访客接收消息,则访客在收到了文本消息后将消息内容发送给APP处理,APP处理完成后调用callEchatJs的visitorReceiveMsg来通知H5将消息内容显示在页面上。
页面状态 echatPageStatus jsonString 一洽页面对话事件通知,APP根据此信息来进行标题栏的标题更换以及是否离开一洽页面等操作,在app触发echat页面的返回操作(echatBackEvent)或者H5的返回操作触发时会触发此事件通知。数据定下详见下方页面状态json字段解释
平台客户新消息 platformNewMsg jsonString 当前未在对话页面的客服消息通知,仅在平台版账户中有效,会将所有未在对话页面的客服消息通过此事件通知给app,以便做APP内的消息通知。比如访客正在和A商户聊天B商户的客服发来消息会收到此事件,详见平台客户新消息json字段解释
上传文件凭证 uploadFileInfo jsonString 通知APP上传文件的凭证,如果获取上传文件凭证报错会返回错误信息,具体的返回信息请参照一洽文件上传接口

访客接收消息json示例:

{
    "receiveMsg":"客服发送给访客的消息",
    "msgId":"23421"
}
字段 类型 说明
receiveMsg string 客服发送给访客的消息息。
msgId string 消息ID,此值在APP处理完客服发送的消息后必须原值返回给H5。

访客ID json字段解

示例:

{visitorId:"web2025192",encryptVId:"kU6+npAXUd0="}
字段 解释
visitorId 当前访客的唯一身份ID,例如:web123 sdk456
encryptVId 加密的访客ID,用来和明文ID一起验证访客身份,在部分http接口中需要使用

预览图片json字段解释:
示例:

{urls:[{“bigImg":"http://tqiniu.echatsoft.com/913eb471-fbcf-46c2-8456-378bd1d55899","smallImg":"http://tqiniu.echatsoft.com/913eb471-fbcf-46c2-8456-378bd1d55899?imageView2/1/w/200/h/150","sourceImg":"http://tqiniu.echatsoft.com/913eb471-fbcf-46c2-8456-378bd1d55899","fileName":"3.png”}\],current:1}
字段 属性 解释
urls   可预览的图片信息列表
  bigImg 图片的大图网络地址
  smallImg 图片的缩略图网络地址
  sourceImg 图片的原图网络地址
  fileName 图片的文件名
current   当前需要播放的图片索引位置。索引位置从1开始计数。

视频播放json字段解释:
示例:

{"type":"play","url":"https://***videosrc url","thumbUrl":"https://video"}
字段 解释
type 请求视频操作的类型,目前固定为:play
url 要播放视频资源的网络地址
thumbUrl 要播放视频资源的封面网络地址

链接打开json字段解释

示例:

{url:"https://www.echatsoft.com",openType:"inner",position:1}
字段 解释
url 要打开链接的地址
openType 一洽H5页面未接管时处理此页面的打开方式。
inner:不跳转页面在对话窗口页面的内部打开url
blank:新窗口打开url
position 此属性待支持,支持后可通知给APP链接所在位置

页面状态json字段解释:
在一洽页面发生变化时会触发此事件调用,在load一洽页面的webview要做返回操作时,可通知一洽页面有返回操作,一洽页面会根据自己的状态向app发送echatPageStatus来通知页面状态。

示例:

{"event":1,"eventValue":"{\"companyId\":500029,\"companyName\":\"一洽客服专卖店\",\"chatUrl\":\"https://ps.echatsoft.com/visitor/mobile/chat.html?companyId=500029&platformSign=3rfef3ffsfsdwerwjljl\"}"}
字段 类型 说明
event int 当前对话的事件
1:请求对话,消息盒子点击某个商户或者平台时触发此事件(平台版用户会有此事件)
2:隐藏对话 消息盒子正在进行的对话隐藏后触发(平台版用户会有此事件)
3:返回主页面 ,在访客请求离开一洽页面时触发.
4:进入消息盒子页面(平台版用户会有此事件)
5:进入浏览页面
6:单独对话窗口打开
7:浏览页面对话窗口打开
8:浏览页面隐藏对话窗口
eventValue JsonString 对话事件信息。
event=1,6,7时:
{"companyId":500029,"companyName":"一洽客服专卖店","chatUrl":"https://ps.echatsoft.com/visitor/mobile/chat.html?companyId=500029&platformSign=3rfef3ffsfsdwerwjljl"}
companyId:当前打开对话的账号ID
companyName:当前打开对话的账号名称
event= 2时:空
event=3时:空
event=4时:空
event=5时:空
event=8时:空

平台客户新消息json字段解释:

示例:

{
    "chatUrl":"https://ps.echatsoft.com/visitor/mobile/chat.html?companyId=500029&platformSign=3rfef3ffsfsdwerwjljl",
    "companyId":"500029",
    "companyLogo":"http://pffile.echatsoft.com/group1/M00/00/00/wKgcPFzae_uAA112AAAZUUyrnC0829.png",
    "companyName":"Nike旗舰店",
    "msgContent":"[图片]",
    "msgId":"123321",
    "unreadMsgCount":5
}
字段 类型 说明
companyId string 新消息所属账号ID
companyName string 新消息所属账号名称
companyLogo string 新消息所属账号的logo
msgContent string 新消息内容(可能为空,如果为空,说明只通知了未读消息数量)
msgId string 消息ID 唯一值
chatUrl string 对话窗口地址,打开即可进入当前未读消息的对话页面
unreadMsgCount int 当前消息盒子的未读消息数量(次访客在平台以及平台所有商户的未读消息数量和)

3.2 JS端
在打开一洽的对话窗口后,native可以直接通过webview的方法调用一洽提供的方法
提供方法:callEchatJs(functionInfo)
参数定义:functionInfo为一个json字符串,json字符串包含的key列表如下:

属性名 类型 说明
functionName String 调用的方法名称
value String 不同的方法值的类型不一致,具体见详细的事件定义列表

示例:

callEchatJs("{\"functionName\":\"closeChat\",\"value\":\"\"}");

此对象在页面加载后即可提供给native调用
提供的方法/事件列表:

功能 调用方法 参数类型 说明
设置访客端声音提示 switchSoundTip String 0/1
0:关闭对话网页新消息提示音
1:开启对话页面新消息提示音,关闭后可根据需要在收到js通知APP有新消息时通过native方法播放声音
APP关闭对话 closeChat 通知一洽关闭对话,在收到一洽的对话结束状态后可销毁webview
获取对话账号ID getCompanyId H5收到此消息后会调用Native提供的sendCompanyId方法将公司ID通知给APP
设置图片视频播放方式 setMediaPlayer jsonString json字符串。通知H5,Native是否接管对话过程中产生的图片和视频的播放,如果Native接管H5会调用native的方法来进行图片和视频的播放。
{"video":1,"image":1}
video:0或1 是否接管视频的播放,image:0或1 是否接管图片的播放。0:不接管 1:接管
设置链接打开方式 setLinkOpener jsonString {native:1,positions:"1,2,3"}在指定位置需要打开链接时,调用Native方法打开;
{native:0}:H5页面内自行处理链接
positions:链接所在位置-此位置属性待支持,支持后可设置接管部分位置的链接,目前接口设置后将接管所有位置的链接
设置键盘高度 setKeyboardHeight int APP获取的键盘高度通知给H5,解决app不用webview、以及不同操作系统版本出现的键盘 输入框推起的问题,多用在IOS APP中。
通知H5发送访客消息 visitorSendMsg jsonString 将处理完的消息发送给H5通知H5发送消息。 jsonString示例见:访客发送消息json示例
通知H5接收客服消息 visitorReceiveMsg jsonString 将处理完的消息发送给H5通知H5显示接收消息。 jsonString示例见:访客接收消息json示例
一洽页面返回 echatBackEvent 在访客点击标题栏中的返回键时app通知H5进行返回操作,H5根据对话的状态来通知Native具体的状态(echatPageStatus)来确认是否需要返回主界面或者更改标题栏标题
获取上传文件凭证 getFileUploadInfo 获取窗口外上传文件的上传凭证,js会将结果通过uploadFileInfo返回给APP,具体的返回信息请参照一洽文件上传接口
代访客发送文本消息 sendTextMsg String 代替访客发送一个文本消息给客服(机器人客服、人工客服),如果当前不在会话中,此方法调用会失效,可通过监听会话状态做个性化提示处理。
互动窗口打开链接 openLink String 通知对话窗口在互动窗口打开页面,在接管了URL打开,但是有些URL又需要在对话窗口的iframe中打开时调用此方法打开

visitorSendMsg方法访客发送消息json示例(注:只有接管了访客发送消息的APP调用此方法才生效):

{
    "sendMsg":"发送给客服的消息",
    "localMsg":"访客本地显示的消息"
}
字段 类型 说明
sendMsg string 发送给客服的消息,此消息可为空如果为空的话则不向客服发送消息。
localMsg string 客服本地显示的消息,此消息不可为空。为空的话此次消息将发送失败。

访客接收消息json示例:

{
    "receiveMsg":"处理过后的客服发送给访客的消息",
    "msgId":"23421"
}
字段 类型 说明
receiveMsg string 客服发送给访客的消息,此消息可为空如果为空的话则访客窗口不显示此消息。
msgId string 调用app的visitorPreReceiveMsg时发送给APP的msgId。如果返回一个不存在的msgId则此次消息会放弃处理。

3.3 调用方法示例

3.3.1 Android示例

//注入对象
        /**
         * 给Webview提供EchatJsBridge的JavaScript对象
         */
        try {
            getWebView().addJavascriptInterface(new JavaScriptInterface(), "EchatJsBridge");
        } catch (Exception e) {
            LogUtils.e(e);
        }

       /**
        *需实现一个class 对象,并在callEchatNative方法上增加@JavascriptInterface注解
        *这里创建了一个名为JavaScriptInterface的class,里面有callEchatNative方法
        */
        class JavaScriptInterface {


            @JavascriptInterface
            public void callEchatNative(final String message) {
                     /**
                     * 解析前端HTML5 通过EchatBridge传入的数据
                     */
                    JSONObject msg = JsonUtil.fromJson(message);
                    String functionName = null;
                    String value = null;
                    try {
                        functionName = msg.getString("functionName");
                        value = msg.getString("value");
                    } catch (JSONException e) {
                        LogUtils.e("解析JSON失败", e.getLocalizedMessage());
                    }

                    if (functionName == null) return;

                    //这里根据获得functionName 进行相应的操作
            }
        }
   /**
     * 初始化聊天窗口
     */
    private void initChatView() {

        //初始化Webview设置
        WebSettings settings = webview.getSettings();
        //开启JavaScript支持
        settings.setJavaScriptEnabled(true);
        //默认设置为true,即允许在 File 域下执行任意 JavaScript 代码
        settings.setAllowFileAccess(true);
        //Disable zoom
        settings.setSupportZoom(false);
        //提高渲染优先级
        settings.setRenderPriority(WebSettings.RenderPriority.HIGH);
        //建议缓存策略为,判断是否有网络,有的话,使用LOAD_DEFAULT
        settings.setCacheMode(WebSettings.LOAD_DEFAULT);//网络正常时使用默认缓存策略


        // 开启DOM storage API 功能
        settings.setDomStorageEnabled(true);
        // 开启database storage API功能
        settings.setDatabaseEnabled(true);

        // 开启Application Cache功能
        settings.setAppCacheEnabled(true);
        settings.setAppCacheMaxSize(1024 * 1024 * 10);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            settings.setAllowUniversalAccessFromFileURLs(true);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }

        //允许iframe与外部域名不一致的时候出现的 请求丢失cookie
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            CookieManager.getInstance().setAcceptThirdPartyCookies(getWebView(), true);
        }

        /**
         * 给Webview提供EchatJsBridge的JavaScript对象
         */
        try {
            getWebView().addJavascriptInterface(new JavaScriptInterface(), "EchatJsBridge");
        } catch (Exception e) {
            LogUtils.e(e);
        }

    }
//调用js方法关闭对话
public void exceJSFunction(String funName, String content) {
        String trigger = "javascript:" + funName + "(" + content + ")";
        loadJS(trigger);
}
private void loadJS(String trigger) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            mWebView.evaluateJavascript(trigger, null);
        } else {
            mWebView.loadUrl(trigger);
        }
}
//通知H5 关闭对话
exceJSFunction("callEchatJs", "{\"functionName\":\"closeChat\"}");


//支持mail、tel协议、支持上传文件
/**
 *创建WebViewClient对象,对shouldOverrideUrlLoading方法进行复写(注意这里区分Android5.0以前和之后),将这个对象设置进webview
 */
webView.setWebViewClient(mWebViewClient);
public WebViewClient mWebViewClient = new WebViewClient() {
        //>= Android 5.0
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
            return WebViewFragment.this.shouldOverrideUrlLoading(view, request);
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return WebViewFragment.this.shouldOverrideUrlLoading(view, url);
        }
}

//这里是统一处理了,在Fragment中创建了一个统一方法。
public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("tel:")) {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            startActivity(intent);
            return;
        }

        if (url.startsWith("mailto:")) {
            openMail(url);
            return;
        }

        if (url.startsWith("http")) {
            view.loadUrl(url);
        }
        return true;
    }

/**
 * 跳转打电话界面
 *
 * @param number
 */
private void callPhone(String number) {
    //打开电话窗口需要用户手动点击拨打
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("tel:" + number));
    startActivity(intent);
}

/**
 * 打开邮件
 *
 * @param mailUrl
 */
private void openMail(String mailUrl) {
    try {
        Intent intent = new Intent(Intent.ACTION_SENDTO);
        intent.setData(Uri.parse(mailUrl));
        startActivity(intent);
    } catch (Exception e) {

    }
}


//上传文件(图片/视频)

//创建WebChromeClient对象,对openFileChooser方法进行复写,注意这里不同系统大版本,使用的方法参数不一样


//这里将Android 3.0-4.4、4.4.1、4.4.3、4.4.4进行统一处理,Android 4.4之后的再进行统一处理(注Android 4.4.2 因系统BUG,不会调用openFileChooser)
public WebChromeClient mWebChromeClient = new WebChromeClient() {

     // For Android  >= 3.0
    public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType) {
        WebViewFragment.this.openFileChooser(valueCallback, acceptType, null);
    }

    //For Android  >= 4.1
    public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
        WebViewFragment.this.openFileChooser(valueCallback, acceptType, capture);
    }

    // For Android >= 5.0
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                     FileChooserParams fileChooserParams) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return WebViewFragment.this.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        }
        return false;
    }

}

private ValueCallback<Uri> uploadMessage;
private ValueCallback<Uri[]> uploadMessageAboveL;
/**
 * 3.0-4.4, 4.4.4 input type=file选择文件时调用 用于上传
 *
 * @param valueCallback
 * @param acceptType
 * @param capture
 */
@Override
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
    //全局保存回调接口
    uploadMessage = valueCallback;
    //调用图库 或 拍照 或 文件
    openCameraOrGalleryOrFile();
}

/**
 * 5.0之后 input type=file选择文件时调用 用于上传
 *
 * @param webView
 * @param filePathCallback
 * @param fileChooserParams
 * @return
 */
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
    //全局保存回调接口
    uploadMessageAboveL = filePathCallback;
    //调用图库 或 拍照 或 文件
    openCameraOrGalleryOrFile();
    return true;
}

public void openCameraOrGallery() {

    //注意Android 6.0新增权限管理,对文件操作要申请读权限(请自行申请)
    //这里通过开源图库选择器 知乎图库作为示例
     Matisse.from(this)
                    .choose(MimeType.ofAll(), false)
                    .countable(true)
                    .capture(true)
                    .captureStrategy(
                            new CaptureStrategy(true, getmActivity().getPackageName() + ".fileprovider"))//用于Android7.0 拍照
                    .maxSelectable(1)//目前HTML5只支持单文件上传
                    .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
                    .gridExpectedSize(
                            getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
                    .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
                    .thumbnailScale(0.85f)
                    .imageEngine(new GlideEngine())
                    .forResult(REQUEST_CODE_CHOOSE);
}

private Uri result;

 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
         /**
         * 处理知乎图库
         */
        if (requestCode == REQUEST_CODE_CHOOSE) {

            if (resultCode == RESULT_OK) {
                List<String> list = Matisse.obtainPathResult(data);
                if (list.size() > 0) {
                    //这里可以考虑对图片进行压缩在上传
                    result = Uri.fromFile(new File(list.get(0)));
                    LogUtils.w(result.toString());
                }
            }
            /**
             * 
             * openFileChooser
             * onShowFileChooser
             * 回调对象执行onReceiveValue
             */
            endToUpload();
        }
}

/**
 * 选择文件/照片回调结束
 */
private void endToUpload() {
    if (uploadMessage != null) {
        uploadMessage.onReceiveValue(result);
        uploadMessage = null;
        result = null;
    }
    if (uploadMessageAboveL != null) {
        Uri[] results = result != null ? new Uri[]{result} : null;
        uploadMessageAboveL.onReceiveValue(results);
        uploadMessageAboveL = null;
        result = null;
    }
}

3.3.2 Ios-UIWebview示例

//注入对象
      // - 获取上下文
      self.context = [self.webViewvalueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
      // - 注入对象
      self.context[@"EchatJsBridge"] = self.jsBridge;

      //注入的对象引入#import <JavaScriptCore/JavaScriptCore.h>并声明如下协议
      @protocol JsBridgeJSExport <JSExport>

        - (void)callEchatNative:(id)json;

      @end
//调用js方法关闭对话
//注入对象遵守此协议<JsBridgeJSExport>并实现- (void)callEchatNative:(id)json;

NSDictionary *dic = @{@"functionName”:@“closeChat”,@“value":@""};
       NSString *jsonStr = [self convertToJsonData:dic];//dict->jsonString
       JSValue *allback = self.context[@“callEchatJs”];
       //oc->js
       [allback callWithArguments:@[jsonStr]];

      - (NSString *)convertToJsonData:(NSDictionary *)dict{
        NSError *error;
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error];
        NSString *jsonString;
        if (!jsonData) {
          NSLog(@"%@",error);
        }else{
          jsonString = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];
        }
        NSMutableString *mutStr = [NSMutableString stringWithString:jsonString];
        NSRange range = {0,jsonString.length};
        //去掉字符串中的空格
        [mutStr replaceOccurrencesOfString:@" " withString:@"" options:NSLiteralSearch range:range];
        NSRange range2 = {0,mutStr.length};
        //去掉字符串中的换行符
        [mutStr replaceOccurrencesOfString:@"\n" withString:@"" options:NSLiteralSearch range:range2];
        return mutStr;
       }
//接收js的事件通知

      - (void)callEchatNative:(id)json{
        //传入的是jsonString进行JsonString转字典
        NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
        NSError *err;
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData
                                                     options:kNilOptions
                                                       error:&err];
        //dict样式形如{“functionName":"chatStatus","value":"robot"} functionName对应方法名,value对应传递的参数,如下粗略写法
        if(方法名isEqual){调用自定义方法并传递参数}
       }
//支持mail、tel协议
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
       //tel或者mail协议在此拦截
       // request.URL.absoluteString   样例tel:188XXXX1234 
       return YES;
      }

3.3.3 Ios-WKWebview示例

//注入对象
      WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc]init];
      config.userContentController = [[WKUserContentController alloc]init];

      //self.jsBridge为接受消息对象callEchatNative是JSPostMessage的对象   
      [config.userContentController addScriptMessageHandler:self.jsBridge name:@"callEchatNative"];
//调用js方法关闭对话
      NSDictionary *dic = @{@"functionName”:@“closeChat”,@“value":@""};
      NSString *jsonStr = [self convertToJsonData:dic];//dict->jsonString
      //oc->js
      [self.webView evaluateJavaScript:[NSString stringWithFormat:@"window.callEchatJs(%@)",closeChatString] completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        if (error) {
            NSLog(@"CALLJS  error = %@---%@\n%@",error,result,functionName);

        }
      }];
//接收js的事件通知
      //->消息接受self.jsBridge 实现 引入#import <WebKit/WebKit.h>并遵守<WKScriptMessageHandler>协议
      -(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
        //在里面解析JS传递给native的数据
      }
//支持mail、tel协议
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
        //navigationAction.request.URL.absoluteString  与UIWeb处理类似
        decisionHandler(WKNavigationActionPolicyAllow);
       }

4.APP接入注意事项

4.1 申请权限
Android:

//文件读写
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
//非系统相机增加权限
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />

Ios:

Privacy - Camera Usage Description 
Privacy - Microphone Usage Description 
Privacy - Photo Library Additions Usage Description 
Privacy - Photo Library Usage Description

4.2 webView设置

以下设置必须支持,如不支持可能会导致聊天业务部分功能不可用。

android:

WebSettings settings = webview.getSettings();
//开启JavaScript支持
settings.setJavaScriptEnabled(true);
//允许读取和写入文件
settings.setAllowFileAccess(true);
//禁用zoom,不支持缩放
settings.setSupportZoom(false);
//支持localstorage来保存客户端的聊天记录
settings.setDomStorageEnabled(true);
// 开启Application Cache功能
settings.setAppCacheEnabled(true);
settings.setAppCacheMaxSize(1024 * 1024 * 10);
//设置支持MixedContent,快捷回复或者其他的消息中可能包含http的图片,如不支持聊天内容中的http图片资源会显示加载失败
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
//支持文件选择,具体实现示例见 3.3.1 Android示例
public WebChromeClient mWebChromeClient = new WebChromeClient() {

     // For Android  >= 3.0
    public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType) {
        WebViewFragment.this.openFileChooser(valueCallback, acceptType, null);
    }

    //For Android  >= 4.1
    public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
        WebViewFragment.this.openFileChooser(valueCallback, acceptType, capture);
    }

    // For Android >= 5.0
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                     FileChooserParams fileChooserParams) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return WebViewFragment.this.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        }
        return false;
    }

}

//支持tel mail协议 ,具体实现示例见 3.3.1 Android示例
webView.setWebViewClient(mWebViewClient);
public WebViewClient mWebViewClient = new WebViewClient() {
        //>= Android 5.0
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
            return WebViewFragment.this.shouldOverrideUrlLoading(view, request);
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return WebViewFragment.this.shouldOverrideUrlLoading(view, url);
        }
}

public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("tel:")) {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            startActivity(intent);
            return;
        }

        if (url.startsWith("mailto:")) {
            openMail(url);
            return;
        }

        if (url.startsWith("http")) {
            view.loadUrl(url);
        }
        return true;
    }

Ios:

//UIWebView 支持mail、tel协议
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
       //tel或者mail协议在此拦截
       // request.URL.absoluteString   样例tel:188XXXX1234 
       return YES;
      }
//WKWebView 支持mail、tel协议
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
        //navigationAction.request.URL.absoluteString  与UIWeb处理类似
        decisionHandler(WKNavigationActionPolicyAllow);
       }

4.3 UI适配

5.示例代码

Android Demo 源码下载
Android QuickStart 详细文档