Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions ios/Sources/GutenbergKit/Sources/Cache/CachedAssetSchemeHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Foundation
import WebKit

class CachedAssetSchemeHandler: NSObject, WKURLSchemeHandler {
nonisolated static let cachedURLSchemePrefix = "gbk-cache-"
nonisolated static let supportedURLSchemes = ["gbk-cache-http", "gbk-cache-https"]

nonisolated static func originalHTTPURL(from url: URL) -> URL? {
guard let scheme = url.scheme, supportedURLSchemes.contains(scheme) else { return nil }

guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return nil
}

components.scheme = String(scheme.suffix(from: scheme.index(scheme.startIndex, offsetBy: cachedURLSchemePrefix.count)))
return components.url
}

nonisolated static func cachedURL(forWebLink link: String) -> String? {
if link.starts(with: "http://") || link.starts(with: "https://") {
return cachedURLSchemePrefix + link
}
return nil
}

let worker: Worker

init(library: EditorAssetsLibrary) {
self.worker = .init(library: library)
}

func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) {
Task {
await worker.start(urlSchemeTask)
}
}

func webView(_ webView: WKWebView, stop urlSchemeTask: any WKURLSchemeTask) {
Task {
await worker.stop(urlSchemeTask)
}
}

actor Worker {
struct TaskInfo {
var webViewTask: WKURLSchemeTask
var fetchAssetTask: Task<Void, Never>

func cancel() {
fetchAssetTask.cancel()
}
}

let library: EditorAssetsLibrary
var tasks: [ObjectIdentifier: TaskInfo] = [:]

init(library: EditorAssetsLibrary) {
self.library = library
}

deinit {
for (_, task) in tasks {
task.cancel()
}
}

func start(_ task: WKURLSchemeTask) {
guard let url = task.request.url, let httpURL = CachedAssetSchemeHandler.originalHTTPURL(from: url) else {
task.didFailWithError(URLError(.badURL))
return
}

let taskKey = ObjectIdentifier(task)

let fetchAssetTask = Task { [library, weak self] in
do {
let (response, content) = try await library.cacheAsset(from: httpURL, webViewURL: url)

await self?.tasks[taskKey]?.webViewTask.didReceive(response)
await self?.tasks[taskKey]?.webViewTask.didReceive(content)

await self?.finish(with: nil, taskKey: taskKey)
} catch {
await self?.finish(with: error, taskKey: taskKey)
}
}
tasks[taskKey] = .init(webViewTask: task, fetchAssetTask: fetchAssetTask)
}

func stop(_ task: WKURLSchemeTask) {
let taskKey = ObjectIdentifier(task)
tasks[taskKey]?.cancel()
tasks[taskKey] = nil
}

private func finish(with error: Error?, taskKey: ObjectIdentifier) {
guard let task = tasks[taskKey] else { return }

if let error {
task.webViewTask.didFailWithError(error)
} else {
task.webViewTask.didFinish()
}
tasks[taskKey] = nil
}
}
}

30 changes: 30 additions & 0 deletions ios/Sources/GutenbergKit/Sources/Cache/EditorAssetsProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation
import WebKit

class EditorAssetsProvider: NSObject, WKScriptMessageHandlerWithReply {
let library: EditorAssetsLibrary

init(library: EditorAssetsLibrary) {
self.library = library
}

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping @MainActor @Sendable (Any?, String?) -> Void) {
guard let payload = message.body as? NSDictionary,
let asset = payload.object(forKey: "asset") as? String,
asset == "manifest"
else {
replyHandler(nil, "Unexpected message")
return
}

Task.detached { [library] in
do {
let data = try await library.manifestContentForEditor()
let dict = try JSONSerialization.jsonObject(with: data)
await replyHandler(dict, nil)
} catch {
await replyHandler(nil, error.localizedDescription)
}
}
}
}
133 changes: 0 additions & 133 deletions ios/Sources/GutenbergKit/Sources/EditorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -423,136 +423,3 @@ private final class GutenbergEditorController: NSObject, WKNavigationDelegate, W
}
}
}

private class EditorAssetsProvider: NSObject, WKScriptMessageHandlerWithReply {
let library: EditorAssetsLibrary

init(library: EditorAssetsLibrary) {
self.library = library
}

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping @MainActor @Sendable (Any?, String?) -> Void) {
guard let payload = message.body as? NSDictionary,
let asset = payload.object(forKey: "asset") as? String,
asset == "manifest"
else {
replyHandler(nil, "Unexpected message")
return
}

Task.detached { [library] in
do {
let data = try await library.manifestContentForEditor()
let dict = try JSONSerialization.jsonObject(with: data)
await replyHandler(dict, nil)
} catch {
await replyHandler(nil, error.localizedDescription)
}
}
}
}

class CachedAssetSchemeHandler: NSObject, WKURLSchemeHandler {
nonisolated static let cachedURLSchemePrefix = "gbk-cache-"
nonisolated static let supportedURLSchemes = ["gbk-cache-http", "gbk-cache-https"]

nonisolated static func originalHTTPURL(from url: URL) -> URL? {
guard let scheme = url.scheme, supportedURLSchemes.contains(scheme) else { return nil }

guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return nil
}

components.scheme = String(scheme.suffix(from: scheme.index(scheme.startIndex, offsetBy: cachedURLSchemePrefix.count)))
return components.url
}

nonisolated static func cachedURL(forWebLink link: String) -> String? {
if link.starts(with: "http://") || link.starts(with: "https://") {
return cachedURLSchemePrefix + link
}
return nil
}

let worker: Worker

init(library: EditorAssetsLibrary) {
self.worker = .init(library: library)
}

func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) {
Task {
await worker.start(urlSchemeTask)
}
}

func webView(_ webView: WKWebView, stop urlSchemeTask: any WKURLSchemeTask) {
Task {
await worker.stop(urlSchemeTask)
}
}

actor Worker {
struct TaskInfo {
var webViewTask: WKURLSchemeTask
var fetchAssetTask: Task<Void, Never>

func cancel() {
fetchAssetTask.cancel()
}
}

let library: EditorAssetsLibrary
var tasks: [ObjectIdentifier: TaskInfo] = [:]

init(library: EditorAssetsLibrary) {
self.library = library
}

deinit {
for (_, task) in tasks {
task.cancel()
}
}

func start(_ task: WKURLSchemeTask) {
guard let url = task.request.url, let httpURL = CachedAssetSchemeHandler.originalHTTPURL(from: url) else {
task.didFailWithError(URLError(.badURL))
return
}

let taskKey = ObjectIdentifier(task)

let fetchAssetTask = Task { [library, weak self] in
do {
let (response, content) = try await library.cacheAsset(from: httpURL, webViewURL: url)

await self?.tasks[taskKey]?.webViewTask.didReceive(response)
await self?.tasks[taskKey]?.webViewTask.didReceive(content)

await self?.finish(with: nil, taskKey: taskKey)
} catch {
await self?.finish(with: error, taskKey: taskKey)
}
}
tasks[taskKey] = .init(webViewTask: task, fetchAssetTask: fetchAssetTask)
}

func stop(_ task: WKURLSchemeTask) {
let taskKey = ObjectIdentifier(task)
tasks[taskKey]?.cancel()
tasks[taskKey] = nil
}

private func finish(with error: Error?, taskKey: ObjectIdentifier) {
guard let task = tasks[taskKey] else { return }

if let error {
task.webViewTask.didFailWithError(error)
} else {
task.webViewTask.didFinish()
}
tasks[taskKey] = nil
}
}
}