TWT Visitor SDK
在 App 内打开访客聊天页面。宿主传入 HTTPS 地址后,SDK 使用系统 WebView / WKWebView 加载,并处理导航、权限、下载回传与站点数据清理。
不使用 SDK、App 自建 WebView 打开访客页,见 访客端 WebView 自行接入。
当前版本:0.0.1
| 平台 | 要求 | 集成 |
|---|---|---|
| Android | minSdk 23 | Maven Central:io.github.twt-chat:visitor-sdk:0.0.1 |
| iOS | iOS 15+ | SPM:https://github.com/TWT-Chat/twt-visitor-ios.git(tag 0.0.1,产品 TwtVisitorSDK) |
| Flutter | Flutter ≥ 3.44,Dart ^3.12.2 | Git:https://github.com/TWT-Chat/visitor_flutter.git(ref 0.0.1) |
集成
Android
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
dependencies {
implementation("io.github.twt-chat:visitor-sdk:0.0.1")
}
SDK 已合并 INTERNET、RECORD_AUDIO、CAMERA(required=false)。Android 9 及以下若下载到公共 Downloads,请自行添加 WRITE_EXTERNAL_STORAGE。
iOS
Xcode → File → Add Package Dependencies,填入:
https://github.com/TWT-Chat/twt-visitor-ios.git
Dependency Rule 选择 Exact Version 0.0.1,产品勾选 TwtVisitorSDK。Deployment Target ≥ iOS 15。
在 Info.plist 中声明:
<key>NSMicrophoneUsageDescription</key>
<string>用于发送语音消息</string>
<key>NSCameraUsageDescription</key>
<string>用于拍摄并发送照片或视频</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>用于保存会话图片</string>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
请保持 App Transport Security 默认设置。公开 API 须在主线程调用。
Flutter
dependencies:
visitor_flutter:
git:
url: https://github.com/TWT-Chat/visitor_flutter.git
ref: 0.0.1
flutter pub get
Android 端需配置 mavenCentral()(见上方 Android 集成)。若使用 RepositoriesMode.PREFER_SETTINGS,还须保留 Flutter Engine 仓库 https://storage.googleapis.com/download.flutter.io。iOS 端无需再添加 SPM,按 iOS 一节配置 Info.plist 即可。
快速开始
接入方只需搞清三件事:
url(必填):控制台配置的 URL 地址,必须 HTTPS。- 配置项:SDK 认识的字段(
isApp/language/theme/title/directChatId/newMessageSoundMode)。都有默认值,可不传。 query:给访客页的业务参数。SDK 不解释 key,只负责 UTF-8 编码拼进 URL。匿名访客可以不传。
不要自己拼 ?a=1&b=2。不要把密码、Cookie、AppSecret 或长期 token 放进 URL。
最少打开(只传 url)时 SDK 拼出:
控制台配置的 URL 地址?lang=zh-cn&theme=system
lang=zh-cn、theme=system 由默认配置写入。App 内若要显示关闭按钮,再设 isApp = true。
Android
val handle = TwtVisitorSdk.start(
this,
VisitorConfig(url = "控制台配置的 URL 地址"),
)
start 打开 VisitorActivity 并返回 VisitorHandle。传入非 Activity Context 时会自动加上 FLAG_ACTIVITY_NEW_TASK。下载、新消息等回调见「回调与事件」。
iOS
import TwtVisitorSDK
let config = VisitorConfiguration(
url: URL(string: "控制台配置的 URL 地址")!
)
let handle = try TwtVisitorSDK.present(from: self, configuration: config, delegate: self)
由宿主 dismiss 页面;dismiss 之后 handle 失效。
Flutter
嵌入当前页面:
import 'package:visitor_flutter/visitor_flutter.dart';
final config = VisitorConfiguration(
url: '控制台配置的 URL 地址',
);
Expanded(child: VisitorView(configuration: config))
VisitorView 必须放在有确定尺寸的父布局中(如 Expanded、SizedBox)。
打开独立原生页:
final handle = await VisitorFlutter.present(configuration: config);
独立页没有 close,由系统返回或页面内关闭按钮结束。
配置项
三端字段相同。SDK 会把部分配置写成 URL query,并覆盖 query 里的同名 key。
| 字段 | 默认 | 进 URL? | 说明 |
|---|---|---|---|
url | 空(必填) | 基址 | 控制台配置的 URL 地址,仅 HTTPS。HTTP、file、javascript、自定义 scheme、带 user:pass 的地址会被拒绝 |
query | 空 Map | 原样拼接 | 业务参数,见下一节。不要手拼 ?a=1 |
isApp | false | true 时写 is_app=1 | App 内打开,访客页显示关闭按钮。false 不写 is_app=0 |
language | zh-cn | 每次都写 lang | en / zh-cn / zh-tw / ja / ko / de / fr / pt / ru / es / vi / th / id / ms / tl |
theme | system | 每次都写 theme | light / dark / system。运行时 setTheme 只接受 light / dark |
title | null | 否 | 原生标题栏文案;空则使用网页标题 |
directChatId | null | 有值时写 direct=1&chatid= | 直达会话,并去掉 chat_id |
newMessageSoundMode | web | 一般否 | web:页面播放;native:宿主在新消息回调中播放。走 JS 桥,不靠 URL(Flutter Android present() 会额外带 new_message_sound_mode) |
写入顺序:url 自带 query → config.query → is_app / lang / theme / direct+chatid。后者覆盖前者。
query 业务参数
query 是透明 Map,SDK 只编码拼接,不解释 key。匿名访客可以整份不传。is_app / lang / theme / direct / chatid 用配置项,不要再写进 query。
绑业务用户时:
query = mapOf(
"sbs" to "user-123",
"sbs_mm" to signature,
"ranstr" to randomStr,
"name" to "张三",
)
| 参数 | 必填? | 含义 |
|---|---|---|
sbs | 绑用户时必填 | 业务系统里的用户唯一 ID,访客页用它识别已登录客户 |
sbs_mm | 有 sbs 就要 | sbs 的签名 |
ranstr | 有签名就要 | 参与签名的随机串 |
name | 可选 | 访客姓名,进入客服工作台展示 |
nickname | 可选 | 访客昵称 |
email | 可选 | 访客邮箱 |
phone | 可选 | 访客手机号 |
customer_remark | 可选 | 客户备注,随登录提交给客服侧 |
ext_fk_id | 可选 | 已有访客 ID,用来续上该访客的历史会话 |
referer | 可选 | 来源页 URL(也认 referer_url),客服侧显示为来源链接 |
source_title | 可选 | 来源页标题 |
不要传 visitor_id、source=android / ios / flutter:访客页不读。App 标识用配置项 isApp=true。
会话 API
| 能力 | Android | iOS | Flutter |
|---|---|---|---|
| 打开独立页 | TwtVisitorSdk.start | TwtVisitorSDK.present | VisitorFlutter.present |
| 嵌入 | — | makeEmbeddedController + register | VisitorView |
| 切换主题 | handle.setTheme | handle.setTheme | VisitorFlutter.setTheme / handle.setTheme |
| 下载回传 | handle.reportDownloadStatus | 同左 | VisitorFlutter.reportDownloadStatus |
| 关闭 | handle.close() | handle.close() | 独立页走系统返回;嵌入页由宿主 pop |
| 清理站点数据 | TwtVisitorSdk.clearSiteData | TwtVisitorSDK.clearSiteData | VisitorFlutter.clearSiteData |
下载状态:started / completed / failed / cancelled。requestId 必须与请求一致。可选字段:path / progress / sourceUrl / mimeType / bytes / errorCode / message。iOS 相册完成路径为 photos://localIdentifier。
清理结果:SUCCESS / INVALID_URL / UNSUPPORTED / IN_USE / FAILED(Flutter 为小写驼峰)。IN_USE 表示仍有会话占用该 host。
切换账号:关闭页面 → 等待销毁完成 → clearSiteData 成功 → 再打开下一个账号。
回调与事件
回调在主线程。请按 requestId / messageId 去重;页面销毁时把未完成的下载标为 failed。
Android VisitorBridgeListener
onDownloadRequested:开始下载,完成后reportDownloadStatusonNewMessage:新消息onBack():返回键。true表示宿主已处理onPermissionResult:权限结果
iOS VisitorBridgeDelegate(弱引用)
visitorDownloadRequested(...)visitorNewMessage()(可能重复)visitorBack() -> BoolvisitorPermissionResult(...)
Flutter VisitorFlutter.events
无监听时不缓存事件,进入页面即 listen,离开时 cancel。
late final StreamSubscription<VisitorBridgeEvent> sub;
@override
void initState() {
super.initState();
sub = VisitorFlutter.events.listen((e) {
switch (e.type) {
case 'downloadRequested':
break;
case 'webReady':
case 'webLoadFailed':
case 'close':
case 'new_message':
break;
}
});
}
@override
void dispose() {
sub.cancel();
super.dispose();
}
事件名:downloadRequested、webReady、webLoadFailed、close、new_message、theme。
嵌入式页面处理返回:
PopScope(
canPop: false,
onPopInvokedWithResult: (_, __) async {
if (!await VisitorFlutter.handleBack()) {
if (context.mounted) Navigator.of(context).pop();
}
},
child: VisitorView(configuration: config),
)
handleBack() == true 表示页面已消费返回(例如从会话详情回到列表)。首次加载失败可调用 VisitorFlutter.reload()。
行为说明
导航 同域 HTTPS 留在容器内;跨域 HTTPS 使用系统浏览器;其它 scheme 拦截。
存储 Cookie、LocalStorage、IndexedDB 默认持久化。第三方 Cookie 与混合内容关闭。
权限 仅同域 HTTPS 的麦克风 / 摄像头采集会进入系统授权,跨域一律拒绝。 例外:Flutter 在 Android 上的嵌入页只允许麦克风,摄像头会被拒绝。需要拍摄时请使用独立页或原生 SDK。
文件选择 走系统 / WebKit 文件选择器,支持单选与多选。
下载 WebView 下载与页面内下载都会回调给宿主,由宿主执行后再回传状态。
返回 先交给页面(约 300ms,用于关闭预览等)。页面不处理再询问宿主;宿主也不处理才关闭容器。
常见问题
为什么打开失败?
检查地址是否为 HTTPS,以及 query 的 key 是否合法。iOS 还会校验 directChatId。
为什么清不掉站点数据?
会话仍在会返回 IN_USE。先关闭全部相关页面,等销毁完成后再清理。iOS 按 host 匹配 WebKit 记录,同一记录下的兄弟子域可能一并清除,但不会做全局清空。
嵌入的 Flutter 页面高度为 0?
给 VisitorView 明确高度,例如包在 Expanded 里。
如何直达某个会话?
设置 directChatId。
如何绑定已登录用户?
在 query 里传 sbs、sbs_mm、ranstr。不要传 visitor_id。
如何在 App 内显示关闭按钮?
isApp = true。
主题如何跟随系统?
初始使用 theme = system。运行中只能切到 light 或 dark。
弱网、后台、锁屏、WebSocket? 请在真机上验证。SDK 不额外封装网络层。
是否支持截屏桥、图片剪贴板? 不提供截屏桥。图片剪贴板不保证所有系统 WebView 都能使用。