purser文档

App 客服页

在你的 iOS、Android、React Native 或 Flutter App 里用 WebView 打开同一个 AI 客服窗口。

App 里不用装 SDK:用 WebView 打开一个网页,就是和网站同一个聊天窗口,主题、知识库、收件箱都一样。控制台「接入」页的「5. 装到你的 App(H5 客服页)」里有你的完整链接。

链接格式

https://askpurser.com/c/<公开密钥>?vid=<访客 ID>&lang=zh-CN#token=<身份令牌>
部分说明
<公开密钥>工作区的公开密钥,以 pk_ 开头。密钥不存在时页面返回 404
vid访客 ID。App 自己生成一个 UUID 存在本地,每次打开都用同一个。不传的话页面会在 WebView 里自己生成一个,但那样你就没法为它签身份令牌
lang窗口界面语言,例如 zh-CN、ja。不传就按 WebView 的系统语言
#token=已登录顾客的身份令牌。没登录就不带,是匿名对话
close=1可选。App 没装下面任何一种原生桥时,加上它会显示关闭按钮,点了返回上一页

App 客服页不受「允许的网站」限制,正式工作区不填也能用。

已登录的顾客

  1. 顾客在 App 里打开客服时,App 向你的服务器要一个身份令牌,并把 vid 一起传过去。
  2. 你的服务器用工作区的身份签名密钥签令牌,visitorId 填这个 vid。签法和网站完全一样,见 身份令牌。
  3. App 把令牌放在链接的 #token= 后面打开页面。

# 后面的部分不会发给任何服务器,页面读到后会立刻从地址栏里擦掉。令牌只能用一次,最长 10 分钟有效(SDK 默认签 5 分钟),所以每次打开页面都要新签一个。令牌无效时对话按匿名处理,不会报错;没有令牌就是一段新的匿名对话,不会接着上一位顾客的对话。

也可以不放在链接里,而是在页面脚本运行前(例如 iOS 的 WKUserScript 在 document start 注入)设置 window.__PURSER_IDENTITY__ = "<令牌>"。

在浏览器里测试

只改 # 后面部分的跳转不会重新加载页面。测试「换一个令牌」时,先跳到别的页面再打开新链接。

关闭页面

窗口右上角的关闭按钮通过你在 App 里装的原生桥通知 App。页面按下面的顺序找桥,找到第一个就用它:

平台桥页面发出的内容
iOS(WKWebView)webkit.messageHandlers.purser{ type: "close" }
Android(WebView)PurserAndroid.close()直接调用 close()
React Native(react-native-webview)ReactNativeWebView.postMessage字符串 {"type":"purser:close"}
Flutter(webview_flutter)JavaScript 通道 Purser字符串 close
Flutter(flutter_inappwebview)callHandler("purser", "close")参数 close

页面加载时检查有没有桥:桥要在加载页面之前装好。一个都没有时不显示关闭按钮(除非链接带了 close=1)。

iOS(WKWebView)

import UIKit
import WebKit

final class SupportViewController: UIViewController, WKScriptMessageHandler {
    var chatURL: URL!  // https://askpurser.com/c/pk_…?vid=…&lang=…#token=…

    override func viewDidLoad() {
        super.viewDidLoad()
        let config = WKWebViewConfiguration()
        // 在加载页面之前注册,页面才会显示关闭按钮
        config.userContentController.add(self, name: "purser")
        let webView = WKWebView(frame: view.bounds, configuration: config)
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(webView)
        webView.load(URLRequest(url: chatURL))
    }

    func userContentController(_ controller: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        if let body = message.body as? [String: Any], body["type"] as? String == "close" {
            dismiss(animated: true)
        }
    }
}

iOS 上顾客可以直接在窗口里选照片发送。

Android(WebView)

class SupportActivity : AppCompatActivity() {
    private var fileCallback: ValueCallback<Array<Uri>>? = null
    private val pickImage =
        registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
            fileCallback?.onReceiveValue(uri?.let { arrayOf(it) } ?: emptyArray())
            fileCallback = null
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val webView = WebView(this)
        setContentView(webView)
        webView.settings.javaScriptEnabled = true
        webView.settings.domStorageEnabled = true

        // 在 loadUrl 之前注入
        webView.addJavascriptInterface(object {
            @JavascriptInterface
            fun close() = runOnUiThread { finish() }
        }, "PurserAndroid")

        // 让顾客能发照片:WebView 默认不弹文件选择器
        webView.webChromeClient = object : WebChromeClient() {
            override fun onShowFileChooser(
                view: WebView,
                callback: ValueCallback<Array<Uri>>,
                params: FileChooserParams,
            ): Boolean {
                fileCallback = callback
                pickImage.launch("image/*")
                return true
            }
        }

        webView.loadUrl(intent.getStringExtra("chatUrl")!!)
    }
}

Android WebView 不实现 onShowFileChooser 的话,窗口里的「添加照片」没有反应。

React Native(react-native-webview)

import { WebView } from "react-native-webview";

export function SupportScreen({ navigation, chatUrl }) {
  return (
    <WebView
      source={{ uri: chatUrl }}
      // 设置了 onMessage,页面里才有 ReactNativeWebView 桥
      onMessage={(e) => {
        try {
          if (JSON.parse(e.nativeEvent.data).type === "purser:close") navigation.goBack();
        } catch {}
      }}
    />
  );
}

Flutter(webview_flutter)

final controller = WebViewController()
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  ..addJavaScriptChannel('Purser', onMessageReceived: (m) {
    if (m.message == 'close') Navigator.pop(context);
  })
  ..loadRequest(Uri.parse(chatUrl));

用 flutter_inappwebview 的话,注册一个名为 purser 的 JavaScript handler,收到参数 close 时关闭页面。

和网站一样的部分

  • 窗口顶部有 AI 身份告知的开场白,AI 的回复标「AI 助手」,客服的回复标「人工客服」。
  • 界面 12 种语言,AI 用顾客最后一条消息的语言回答。
  • 测试工作区里标题旁显示「测试模式」。
  • 页面会避开刘海和底部横条(safe area),不需要你自己留边距。

详见 网站聊天窗口。

本页内容