-
Notifications
You must be signed in to change notification settings - Fork 137
/
Copy pathWebKitCommunicationBridge.swift
95 lines (80 loc) · 4.16 KB
/
WebKitCommunicationBridge.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*
This source file is part of the Swift.org open source project
Copyright (c) 2021 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
// The WebKitCommunicationBridge is only available on platforms that support WebKit.
#if canImport(WebKit)
import Foundation
public import WebKit
/// Provides bi-directional communication with a documentation renderer via JavaScript calls in a web view.
public struct WebKitCommunicationBridge: CommunicationBridge {
public var onReceiveMessage: ((Message) -> ())? = nil
/// Creates a communication bridge configured with the given controller to receive messages.
/// - Parameter contentController: The controller that receives messages. Set to `nil` if you need the communication bridge
/// to ignore received messages.
/// - Parameter onReceiveMessage: The handler that the communication bridge calls when it receives a message.
/// Set to `nil` if you need the communication bridge to ignore received messages.
public init(
with contentController: WKUserContentController? = nil,
onReceiveMessage: ((Message) -> ())? = nil
) {
guard let onReceiveMessage else {
return
}
self.onReceiveMessage = onReceiveMessage
contentController?.add(
ScriptMessageHandler(onReceiveMessageData: onReceiveMessageData),
name: "bridge"
)
}
/// Sends a message using the given handler using the JSON format.
/// - Parameter message: The message to send.
/// - Parameter evaluateJavaScript: A handler that the communication bridge uses to send the given message, encoded in JSON.
/// - Throws: Throws a ``CommunicationBridgeError/unableToEncodeMessage(_:underlyingError:)`` if the communication bridge could not encode the given message to JSON.
public func send(
_ message: Message,
using evaluateJavaScript: (String, ((Any?, Error?) -> ())?) -> ()
) throws {
do {
let encodedMessage = try JSONEncoder().encode(message)
let messageJSON = String(data: encodedMessage, encoding: .utf8)!
evaluateJavaScript("window.bridge.receive(\(messageJSON))") { _, _ in }
} catch let error {
throw CommunicationBridgeError.unableToEncodeMessage(message, underlyingError: error)
}
}
/// Called by the communication bridge when a message is received by a script message handler.
///
/// Decodes the given WebKit script message as a ``Message``, and calls the ``onReceiveMessage`` handler.
/// The communication bridge ignores unrecognized messages.
/// - Parameter messageBody: The body of a `WKScriptMessage` provided by a `WKScriptMessageHandler`.
func onReceiveMessageData(messageBody: Any) {
// `WKScriptMessageHandler` transforms JavaScript objects to dictionaries.
// Serialize the given dictionary to JSON data if possible, and decode the JSON data to a
// message. If either of these steps fail, the communication-bridge ignores the message.
guard let messageData = try? JSONSerialization.data(withJSONObject: messageBody),
let message = try? JSONDecoder().decode(Message.self, from: messageData) else {
return
}
onReceiveMessage?(message)
}
/// A WebKit script message handler for communication bridge messages.
///
/// When receiving a message, the handler calls the given `onReceiveMessageData` handler with the message's body.
private class ScriptMessageHandler: NSObject, WKScriptMessageHandler {
var onReceiveMessageData: (Any) -> ()
init(onReceiveMessageData: @escaping (Any) -> ()) {
self.onReceiveMessageData = onReceiveMessageData
}
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
onReceiveMessageData(message.body)
}
}
}
#endif