Sidewalk
Proof-of-concept WKWebview.evaluateJavaScript(...)
replacement for WebSocket-based Javascript execution.
Goals
- Workaround for a memory leak in WKWebView
- Provide a different way for JS <-> Swift communication
- Playing with WebSockets
- Playground for async/await in Swift 5.5
Notes
- The project is experimental and full of TODOs !
- Use it for your own risk, test it carefully !
- To send through big amount of data, you need to use
sidewalkJavaScript(data: Data, ...)
method and handle the data in Javascript by yourself. (customMessageHandler
) - There is no HTTPS/WSS support yet. If you inject Sidewalk into a page with HTTPS base URL, the connection will fail due to mixed content error
- There is no
completionHandler
implementation yet ---> not possible to access JS execution result - There is no bidirectional communication yet ---> you can only execute Javascript
- To receive messages from Javascript, you still have to use
WKScriptMessageHandler
TODOs
- add HTTPS/WSS support
- add bidirectional messaging
- add completion handler for script evaluation results (async/await with message ID on top of bidirectional messaging)
- add reconnection logic in case of exceptional socket close
- here will be the collection of TODOs from the code
Sample project
This project highlights the problem with WKWebview.evaluateJavaScript(...)
and shows how to overcome the issue with Sidewalk. https://github.com/Danesz/SidewalkExample.git
How to use
- Install the Swift package
...
dependencies: [
.package(url: "https://github.com/Danesz/Sidewalk.git", from: "0.0.1"),
],
...
- A) Attach it to the webview before it loads the page
import Sidewalk
...
Sidewalk.shared().attachToWebView(webview, when: .atDocumentStart)
webview.loadHTMLString(...)
- B) Or, inject the Javascript manually when needed on the already loaded page
import Sidewalk
...
Sidewalk.shared().injectNow(webview)
- Execute Javascript via Sidewalk on your WKWebview instance. (Sidewalk defines and extension on the WKWebview class)
import Sidewalk
...
//old way
//webview.evaluateJavaScript("console.log('I am here')")
//new way
webview.sidewalkJavaScript("console.log('I am here')")
- Tell to Sidewalk to forget your webview once you don't need the webview anymore. (to avoid memory leaks)
import Sidewalk
...
Sidewalk.shared().forgetWebview(webview)
- (optional) Use custom messaging between JS and Swift. In this example we send JSON messages to Javascript and inside the webview we can decide how to react based on the message type.
In Swift:
import Sidewalk
...
struct SidewalkSocketDirectBodyUpdateMessage: Codable {
let type: String = "bodyUpdate"
var content: String
}
class SidewalkSocketMessageHandlerJSON: SidewalkSocketMessageHandler {
func didReceive(message: WebSocketMessage, onSocket socket: WebSocket) {
fatalError("not supported")
}
func send(data: Data, onSocket socket: WebSocket) {
fatalError("not supported")
}
func send(text: String, onSocket socket: WebSocket) {
do {
let data = try JSONEncoder().encode(SidewalkSocketDirectBodyUpdateMessage(content: text))
if let message = String(data: data, encoding: String.Encoding.utf8) {
socket.send(text: message)
}
} catch let error {
print("SidewalkSocketMessageHandlerJSON error", error.localizedDescription)
}
}
func send(message: WebSocketMessage, onSocket socket: WebSocket) {
fatalError("not supported")
}
}
And receive it in Javascript:
Sidewalk.customMessageHandler = function(event){
let parsed = JSON.parse(event.data);
if (parsed.type === "bodyUpdate") {
document.body.innerHTML = parsed.content;
}
}
Contribution
Any contribution is welcome!