Files
edgeever/apps/ios/EdgeEver/Editor/ResourceBlobStore.swift
T
tianma-if c22064bcea fix(ios): render demo note images including SVG blobs
Pre-hydrate protected image srcs before TipTap setContent so file://
WebView never loads /api paths under sandbox. Detect SVG correctly,
emit data URLs for SVG assets, treat numeric image width as percent,
and keep unit tests off real demo cache keys.
2026-08-07 10:15:10 +08:00

87 lines
3.2 KiB
Swift

import Foundation
import WebKit
/// In-memory blob map so WKWebView can load authenticated images via a custom scheme
/// without embedding multi-megabyte data: URLs into evaluateJavaScript strings.
actor ResourceBlobStore {
static let shared = ResourceBlobStore()
private var blobs: [String: (data: Data, mimeType: String)] = [:]
func put(id: String, data: Data, mimeType: String) {
blobs[id] = (data, mimeType)
}
func get(id: String) -> (data: Data, mimeType: String)? {
blobs[id]
}
func remove(id: String) {
blobs[id] = nil
}
}
/// Serves `edgeever-res://local/<resourceId>` image bytes into the TipTap WKWebView.
final class EdgeEverResourceSchemeHandler: NSObject, WKURLSchemeHandler, @unchecked Sendable {
static let scheme = "edgeever-res"
/// Build a short local URL for a cached resource id.
static func localURL(for resourceId: String) -> String {
let encoded = resourceId.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? resourceId
return "\(scheme)://local/\(encoded)"
}
static func resourceId(from url: URL) -> String? {
guard url.scheme == scheme else { return nil }
// edgeever-res://local/<id>
let path = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
guard !path.isEmpty else { return nil }
return path.removingPercentEncoding ?? path
}
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let url = urlSchemeTask.request.url,
let id = Self.resourceId(from: url)
else {
urlSchemeTask.didFailWithError(
NSError(domain: "EdgeEverResource", code: 400, userInfo: [NSLocalizedDescriptionKey: "Invalid resource URL"])
)
return
}
Task {
guard let entry = await ResourceBlobStore.shared.get(id: id) else {
urlSchemeTask.didFailWithError(
NSError(domain: "EdgeEverResource", code: 404, userInfo: [NSLocalizedDescriptionKey: "Resource not cached"])
)
return
}
// HTTPURLResponse is more reliable for <img> loads than plain URLResponse
// (especially for non-bitmap types served through a custom scheme).
let headers: [String: String] = [
"Content-Type": entry.mimeType,
"Content-Length": String(entry.data.count),
"Access-Control-Allow-Origin": "*",
]
guard let response = HTTPURLResponse(
url: url,
statusCode: 200,
httpVersion: "HTTP/1.1",
headerFields: headers
) else {
urlSchemeTask.didFailWithError(
NSError(domain: "EdgeEverResource", code: 500, userInfo: [NSLocalizedDescriptionKey: "Bad response"])
)
return
}
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(entry.data)
urlSchemeTask.didFinish()
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
// No long-running network task to cancel; blobs are already in memory.
}
}