handle wkmessages and send replies to webview

This commit is contained in:
Satindar Dhillon 2022-03-22 23:48:39 -07:00
parent 7b6c381ce2
commit 3b907d415f
7 changed files with 204 additions and 70 deletions

View file

@ -8,7 +8,7 @@ struct WebReader: UIViewRepresentable {
let articleContent: ArticleContent
let item: FeedItem
let openLinkAction: (URL) -> Void
let webViewActionHandler: (WKScriptMessage) -> Void
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
let navBarVisibilityRatioUpdater: (Double) -> Void
let authToken: String
let appEnv: AppEnvironment

View file

@ -5,36 +5,6 @@ import SwiftUI
import Views
import WebKit
struct SafariWebLink: Identifiable {
let id: UUID
let url: URL
}
// TODO: load highlights
final class WebReaderViewModel: ObservableObject {
@Published var isLoading = false
@Published var articleContent: ArticleContent?
var subscriptions = Set<AnyCancellable>()
func loadContent(dataService: DataService, slug: String) {
isLoading = true
guard let viewer = dataService.currentViewer else { return }
dataService.articleContentPublisher(username: viewer.username, slug: slug).sink(
receiveCompletion: { [weak self] completion in
guard case .failure = completion else { return }
self?.isLoading = false
},
receiveValue: { [weak self] articleContent in
self?.articleContent = articleContent
}
)
.store(in: &subscriptions)
}
}
struct WebReaderContainerView: View {
let item: FeedItem
let homeFeedViewModel: HomeFeedViewModel
@ -62,7 +32,24 @@ struct WebReaderContainerView: View {
)
}
func webViewActionHandler(message: WKScriptMessage) {
func webViewActionHandler(message: WKScriptMessage, replyHandler: WKScriptMessageReplyHandler?) {
if message.name == WebViewAction.readingProgressUpdate.rawValue {
let messageBody = message.body as? [String: Double]
if let messageBody = messageBody, let progress = messageBody["progress"] {
homeFeedViewModel.updateProgress(itemID: item.id, progress: Double(progress))
}
}
if let replyHandler = replyHandler {
viewModel.webViewActionWithReplyHandler(
message: message,
replyHandler: replyHandler,
dataService: dataService
)
return
}
if message.name == WebViewAction.highlightAction.rawValue {
handleHighlightAction(message: message)
}

View file

@ -46,8 +46,8 @@ struct WebReaderContent {
}
window.omnivoreArticle = {
id: "test",
linkId: "test",
id: "\(item.id)",
linkId: "\(item.id)",
slug: "test-slug",
createdAt: new Date().toISOString(),
savedAt: new Date().toISOString(),

View file

@ -7,8 +7,10 @@ import Utils
import Views
import WebKit
typealias WKScriptMessageReplyHandler = (Any?, String?) -> Void
final class WebReaderCoordinator: NSObject {
var webViewActionHandler: (WKScriptMessage) -> Void = { _ in }
var webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void = { _, _ in }
var linkHandler: (URL) -> Void = { _ in }
var needsReload = true
var lastSavedAnnotationID: UUID?
@ -34,41 +36,17 @@ final class WebReaderCoordinator: NSObject {
extension WebReaderCoordinator: WKScriptMessageHandler {
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
webViewActionHandler(message)
webViewActionHandler(message, nil)
}
}
extension WebReaderCoordinator: WKScriptMessageHandlerWithReply {
func userContentController(_: WKUserContentController,
didReceive message: WKScriptMessage,
replyHandler: @escaping (Any?, String?) -> Void)
{
guard let messageBody = message.body as? [String: Any] else { return }
guard let actionID = messageBody["actionID"] as? String else { return }
print("handling message", actionID, messageBody)
switch actionID {
case "deleteHighlight":
// TODO: make API call here, web expects a boolean result.
// we pass results back to JS as the `result` property.
// We are just passing true as an example here. It should
// be false if the API has an error.
replyHandler(["result": true], nil)
// TODO:
// case "createHighlight":
// break
// case "mergeHighlightMutation":
// break
// case "updateHighlightMutation":
// break
// case "articleReadingProgressMutation":
// break
default:
replyHandler(nil, "Unknown actionID: \(actionID)")
}
func userContentController(
_: WKUserContentController,
didReceive message: WKScriptMessage,
replyHandler: @escaping (Any?, String?) -> Void
) {
webViewActionHandler(message, replyHandler)
}
}

View file

@ -0,0 +1,165 @@
import Combine
import Models
import Services
import SwiftUI
import WebKit
struct SafariWebLink: Identifiable {
let id: UUID
let url: URL
}
final class WebReaderViewModel: ObservableObject {
@Published var isLoading = false
@Published var articleContent: ArticleContent?
var subscriptions = Set<AnyCancellable>()
func loadContent(dataService: DataService, slug: String) {
isLoading = true
guard let viewer = dataService.currentViewer else { return }
dataService.articleContentPublisher(username: viewer.username, slug: slug).sink(
receiveCompletion: { [weak self] completion in
guard case .failure = completion else { return }
self?.isLoading = false
},
receiveValue: { [weak self] articleContent in
self?.articleContent = articleContent
}
)
.store(in: &subscriptions)
}
func createHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
dataService.createHighlightPublisher(
shortId: messageBody["shortId"] as? String ?? "",
highlightID: messageBody["id"] as? String ?? "",
quote: messageBody["quote"] as? String ?? "",
patch: messageBody["patch"] as? String ?? "",
articleId: messageBody["articleId"] as? String ?? ""
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func deleteHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
dataService.deleteHighlightPublisher(
highlightId: messageBody["highlightId"] as? String ?? ""
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func mergeHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
dataService.mergeHighlightPublisher(
shortId: messageBody["shortId"] as? String ?? "",
highlightID: messageBody["id"] as? String ?? "",
quote: messageBody["quote"] as? String ?? "",
patch: messageBody["patch"] as? String ?? "",
articleId: messageBody["articleId"] as? String ?? "",
overlapHighlightIdList: messageBody["overlapHighlightIdList"] as? [String] ?? []
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func updateHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
dataService.updateHighlightAttributesPublisher(
highlightID: messageBody["highlightId"] as? String ?? "",
annotation: messageBody["annotation"] as? String ?? "",
sharedAt: nil
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func updateReadingProgress(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
let itemID = messageBody["id"] as? String
let readingProgress = messageBody["readingProgressPercent"] as? Double
let anchorIndex = messageBody["readingProgressAnchorIndex"] as? Int
guard let itemID = itemID, let readingProgress = readingProgress, let anchorIndex = anchorIndex else {
replyHandler(["result": false], nil)
return
}
dataService.updateArticleReadingProgressPublisher(
itemID: itemID,
readingProgress: readingProgress,
anchorIndex: anchorIndex
)
.sink { completion in
guard case .failure = completion else { return }
replyHandler(["result": false], nil)
} receiveValue: { _ in
replyHandler(["result": true], nil)
}
.store(in: &subscriptions)
}
func webViewActionWithReplyHandler(
message: WKScriptMessage,
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
guard let messageBody = message.body as? [String: Any] else { return }
guard let actionID = messageBody["actionID"] as? String else { return }
switch actionID {
case "deleteHighlight":
deleteHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
case "createHighlight":
createHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
case "mergeHighlight":
mergeHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
case "updateHighlight":
updateHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
case "articleReadingProgress":
updateReadingProgress(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
default:
replyHandler(nil, "Unknown actionID: \(actionID)")
}
}
}

View file

@ -10,8 +10,6 @@ public struct ArticleContent {
) {
self.htmlContent = htmlContent
self.highlights = highlights
print(highlightsJSONString)
}
public var highlightsJSONString: String {

View file

@ -9,7 +9,8 @@ public extension DataService {
highlightID: String,
quote: String,
patch: String,
articleId: String
articleId: String,
annotation: String? = nil
) -> AnyPublisher<String, BasicError> {
enum MutationResult {
case saved(id: String)
@ -28,7 +29,12 @@ public extension DataService {
let mutation = Selection.Mutation {
try $0.createHighlight(
input: InputObjects.CreateHighlightInput(
id: highlightID, shortId: shortId, articleId: articleId, patch: patch, quote: quote
id: highlightID,
shortId: shortId,
articleId: articleId,
patch: patch,
quote: quote,
annotation: OptionalArgument(annotation)
),
selection: selection
)