Support livechat in a webview
Support Integration
The support integration is the component that allows customers to communicate
with support operators. Before embedding a support livechat, make sure that
you receive your livechat-id
and tenant-id
from our support.
WebView
with a preconfigured URL. This will automatically
create a support conversation when loaded.Livechat URL structure
`https://${tenantId}-production-messaging-webchat.patty-awseuc1.swops.cloud/${livechatId}/`
To configure the instance, you can provide preconditions, language and other options
as query parameters. The complex values have to be stringified and URL encoded. For
example to pass the preconditions {"foo": "bar"}
, append a query
parameter preconditions=%7B%22foo%22%3A%22bar%22%7D
.
The supported query parameters are preconditions
,
language
and options
Example code to build a embeddable chat URL in JavaScript
const language = 'fr'; const preconditions = encodeURIComponent(JSON.stringify({ language, zip: '1845' })); const options = encodeURIComponent(JSON.stringify({ captureLinks: true })); const url = `https://${tenantId}-production-messaging-webchat.patty-awseuc1.swops.cloud/${livechatId}/` + `?language=${language}` + `&preconditions=${preconditions}` + `&options=${options}`;
If the livechat is embedded via URL, we emit the following events using the postMessage API. Messages are serialized as JSON objects.
type MessageEventData =
{
type: string;
data: {[key: string]: any};
_by: 'smoope';
} &
(
| ReadyToSupportData
| CapturedLinkData
| RoomClosedData
| SupportRequestRejectedData
| RequestTokenData
);
// Example MessageEvent
// {"type":"ReadyToSupport","data":{"room":"01CXWBKT6Q6YCV1X12FNSJ0QDR"},"_by":"smoope"}
ReadyToSupport
interface ReadyToSupportData {
type: 'ReadyToSupport';
data: { room: string }
}
CapturedLink
interface CapturedLinkData {
type: 'CapturedLink';
data: { url: string }
}
RoomClosed
interface RoomClosedData {
type: 'RoomClosed';
data: {}
}
SupportRequestRejected
interface SupportRequestRejectedData {
type: 'SupportRequestRejected';
data: {
title: string;
description: string;
reason: 'NO_OPERATOR_AVAILABLE' | 'OUTSIDE_BUSINESS_HOURS' | 'SERVER_ERROR'
}
}
RequestToken
interface RequestTokenData {
type: 'RequestToken';
data: {};
}
We emit the following types via postMessage.
-
ReadyToSupport
, if the chat is initialized far enough to accept support input from the user -
CapturedLink
, if thecaptureLinks
option is enabled with{url: string}
as event payload -
RoomClosed
, if the current support room is closed -
SupportRequestRejected
, if the current support request got rejected -
RequestTokenData
, if the provided token became invalid
Due to missing bridge between JavaScript and Android/iOS, we have to write some custom code to listen for emitted events. (See "Android Setup" and "iOS Setup")
Android Setup
In Android, you have to define a JavaScript interface to inject into the WebView.
Kotlin
class SmoopeIpc {
@JavascriptInterface
fun postMessage(data: String, origin: String) {
// handle livechat event
println("Smoope chat emitted data: $data $origin")
}
}
Java
class SmoopeIpc { @JavascriptInterface public void postMessage(String data, String origin) { // handle livechat event System.out.println("Smoope chat emitted data: " + data + " " + origin); } }
Using the addJavascriptInterface, register your
Interface
as SMOOPE_IPC
. This allows us to
call your Interface, if defined.
Kotlin
webView.settings.javaScriptEnabled = true
webView.webViewClient = WebViewClient()
webView.addJavascriptInterface(SmoopeIpc(), "SMOOPE_IPC")
webView.loadUrl("<webchat-url>")
Java
webView.getSettings().setJavaScriptEnabled(true); webView.setWebViewClient(new WebViewClient()); webView.addJavascriptInterface(new SmoopeIpc(), "SMOOPE_IPC"); webView.loadUrl("<webchat-url>");
File upload
If you want to allow the user to select a file, you have to make some
adjustments. First set a WebChromeClient
on the
WebView
instance.
Next you can implement the onShowFileChooser
to fit your
use-case.
iOS Setup
Similar to Android, in iOS, you have to register a listener for a custom
messageHandler
method on a WKWebView. Use the
SMOOPE_IPC
name to add to the
WKUserContentController
.
Setup the WKWebView to receive livechat events:
Swift
let config = WKWebViewConfiguration()
config.preferences.javaScriptEnabled = true
let userContentController = WKUserContentController()
userContentController.add(self, name: "SMOOPE_IPC")
config.userContentController = userContentController
webView.load(URLRequest(url: URL(string: "<webchat-url>")!))
Objective-C
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[[config preferences] setJavaScriptEnabled:true];
WKUserContentController * userCOntentController = [[WKUserContentController alloc] init];
[userCOntentController addScriptMessageHandler:self name:@"SMOOPE_IPC"];
[config setUserContentController:userCOntentController];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"<webchat-url>"]]];
Listen for messages with the SMOOPE_IPC
name:
Swift
extension ViewController: WKScriptMessageHandler {
func userContentController(_
userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
if message.name == "SMOOPE_IPC", let messageBody = message.body as? String {
// handle livechat event
print(messageBody)
}
}
}
Objective-C
@interface ViewController (ScriptMessageHandler) <WKScriptMessageHandler>
@end
@implementation ViewController (ScriptMessageHandler)
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSString *messageBody = [message body];
if ([[message name] isEqualToString:@"SMOOPE_IPC"] && [messageBody isKindOfClass:[NSString class]]) {
// handle livechat event
NSLog(@"%@", messageBody);
}
}
@end
Opening external links
If you want to allow the livechat to open external links, you have to make additional adjustments. The following snippets allows the webview to open external links by intercepting navigation actions.
Swift
// during the webview setup
self.webview?.navigationDelegate = self
extension ViewController: WKNavigationDelegate {
func webView(_
webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
guard let requestURL = navigationAction.request.url, navigationAction.targetFrame == nil else {
decisionHandler(.allow)
return
}
UIApplication.shared.open(requestURL, options: [:], completionHandler: nil)
decisionHandler(.cancel)
}
}
Objective-C
// during the webview setup
self.webview.navigationDelegate = self;
#pragma mark -- WkWebView Delegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *requestURL = [[navigationAction request] URL];
if (requestURL != nil && [navigationAction targetFrame] == nil) {
if (@available(iOS 10.0, *)) {
[[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:nil];
} else {
[[UIApplication sharedApplication] openURL:requestURL];
}
decisionHandler(WKNavigationActionPolicyCancel);
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
Authenticating a user
In cases where you have an authenticated user and want to pass its identity to our system, you have to provide us with the token.
First you have to tell the app that it should take a token from the outside by
loading it with the needsAuthentication
query parameter set to
true
(needsAuthentication=true
).
In this configuration, the chat will emit a RequestToken
and
wait for the provided token by the RequestTokenResponse
message
from its host.
Kotlin
val myNewValidToken = "0xDEADBEEF"
val data = gson.toJson(
mapOf(
"type" to "RequestTokenResponse",
"data" to mapOf("token" to myNewValidToken),
"_by" to "smoope"
)
)
webView.postWebMessage(WebMessage(data), Uri.parse("*"))
Java
var myNewValidToken = "0xDEADBEEF"; var data = gson.toJson( new HashMap() {{ put("type", "RequestTokenResponse"); put("data", new HashMap() {{ put("token", myNewValidToken); }}); put("_by", "smoope"); }} ); webView.postWebMessage(new WebMessage(data), Uri.parse("*"));
RequestTokenResponse type
interface RequestTokenResponseData {
type: 'RequestTokenResponse';
data: { token: string };
_by: 'smoope';
}
If the provided token expires, the chat emits a RequestToken
event which allows you to refresh the token. To do that, simply post a
RequestTokenResponse
message with the new token.
Posting a message from iOS
Because iOS exposes no way to communicate from iOS context to the WKWebview context, you have to make additional adjustments.
If the app is launched inside a WKWebview, we register a global
wkSmoopePostMessage
method. This allows you to use the
evaluateJavaScript method, to emit
data to the chat.
The method accepts a JSON-Object which contains the message information.
Responding to a token request with a new token
let object: [String : Any] = [
"type": "RequestTokenResponse",
"data": ["token": myNewValidToken],
"_by": "smoope"
]
let string = String(
data: try! JSONSerialization.data(withJSONObject: object),
encoding: .utf8
)!
self.webview?.evaluateJavaScript("wkSmoopePostMessage(\(string))")