Skip to content

Commit bc1de64

Browse files
committed
Decouple swiftsoup from the library
1 parent 601a9a8 commit bc1de64

File tree

13 files changed

+362
-134
lines changed

13 files changed

+362
-134
lines changed

Package.resolved

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,44 @@ let package = Package(
77
name: "GutenbergKit",
88
platforms: [.iOS(.v15), .macOS(.v14)],
99
products: [
10-
.library(name: "GutenbergKit", targets: ["GutenbergKit"])
10+
.library(name: "GutenbergKit", targets: ["GutenbergKit"]),
11+
.library(name: "GutenbergKitAssetManifestParser", targets: ["GutenbergKitAssetManifestParser"])
1112
],
1213
dependencies: [
1314
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.5"),
1415
],
1516
targets: [
1617
.target(
1718
name: "GutenbergKit",
18-
dependencies: ["SwiftSoup"],
19+
dependencies: [],
1920
path: "ios/Sources/GutenbergKit",
2021
exclude: [],
2122
resources: [.copy("Gutenberg")]
2223
),
24+
.target(
25+
name: "GutenbergKitAssetManifestParser",
26+
dependencies: ["GutenbergKit", "SwiftSoup"],
27+
path: "ios/Sources/GutenbergKitAssetManifestParser",
28+
exclude: [],
29+
resources: []
30+
),
2331
.testTarget(
2432
name: "GutenbergKitTests",
2533
dependencies: ["GutenbergKit"],
26-
path: "ios/Tests",
34+
path: "ios/Tests/GutenbergKitTests",
2735
exclude: [],
2836
resources: [
29-
.copy("GutenbergKitTests/Resources/manifest-test-case-1.json")
37+
.copy("Resources/manifest-test-case-1.json")
3038
]
3139
),
40+
.testTarget(
41+
name: "GutenbergKitAssetManifestParserTests",
42+
dependencies: ["GutenbergKitAssetManifestParser"],
43+
path: "ios/Tests/GutenbergKitAssetManifestParserTests",
44+
exclude: [],
45+
resources: [
46+
.copy("../GutenbergKitTests/Resources/manifest-test-case-1.json")
47+
]
48+
)
3249
]
3350
)

ios/Demo-iOS/Gutenberg.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ios/Sources/GutenbergKit/Sources/Cache/CachedAssetSchemeHandler.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ class CachedAssetSchemeHandler: NSObject, WKURLSchemeHandler {
1616
return components.url
1717
}
1818

19-
nonisolated static func cachedURL(forWebLink link: String) -> String? {
20-
if link.starts(with: "http://") || link.starts(with: "https://") {
21-
return cachedURLSchemePrefix + link
19+
nonisolated static func cachedURL(for url: URL) -> URL {
20+
if url.scheme == "http" || url.scheme == "https" {
21+
var components = URLComponents(string: url.absoluteString)!
22+
components.scheme = cachedURLSchemePrefix + (url.scheme ?? "")
23+
return components.url!
2224
}
23-
return nil
25+
26+
return url
2427
}
2528

2629
let worker: Worker
@@ -105,4 +108,3 @@ class CachedAssetSchemeHandler: NSObject, WKURLSchemeHandler {
105108
}
106109
}
107110
}
108-

ios/Sources/GutenbergKit/Sources/Cache/EditorAssetsLibrary.swift

Lines changed: 97 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import Foundation
22
import CryptoKit
3-
import SwiftSoup
43

54
public actor EditorAssetsLibrary {
65
enum ManifestError: Error {
76
case unavailable
87
case invalidServerResponse
8+
case invalidSiteUrl
99
}
1010

1111
let urlSession: URLSession
@@ -47,10 +47,14 @@ public actor EditorAssetsLibrary {
4747
/// - SeeAlso: `EditorAssetsLibrary.addAsset`
4848
func manifestContentForEditor() async throws -> Data {
4949
// For scheme-less links (i.e. '//stats.wp.com/w.js'), use the scheme in `siteURL`.
50-
let siteURLScheme = URL(string: configuration.siteURL)?.scheme
50+
guard let siteURLScheme = URL(string: configuration.siteURL)?.scheme else {
51+
throw ManifestError.invalidSiteUrl
52+
}
53+
5154
let data = try await loadManifestContent()
52-
let manifest = try JSONDecoder().decode(EditorAssetsMainifest.self, from: data)
53-
return try manifest.renderForEditor(defaultScheme: siteURLScheme)
55+
let manifest = try EditorAssetManifest(data: data)
56+
57+
return try JSONEncoder().encode(manifest.applyingUrlScheme(siteURLScheme, using: configuration.assetManifestParser))
5458
}
5559

5660
/// Fetches all assets in the `EditorConfiguration.editorAssetsEndpoint` manifest and stores them on the device.
@@ -61,30 +65,27 @@ public actor EditorAssetsLibrary {
6165
let siteURLScheme = URL(string: configuration.siteURL)?.scheme
6266

6367
let data = try await loadManifestContent()
64-
let manifest = try JSONDecoder().decode(EditorAssetsMainifest.self, from: data)
65-
let assetLinks = try manifest.parseAssetLinks(defaultScheme: siteURLScheme)
66-
67-
for link in assetLinks {
68-
guard let url = URL(string: link) else {
69-
NSLog("Malformed asset link: \(link)")
70-
continue
71-
}
68+
let manifest = try EditorAssetManifest(data: data)
69+
.applyingUrlScheme(siteURLScheme, using: configuration.assetManifestParser)
70+
let assetUrls = try manifest.getAllAssetUrls(using: configuration.assetManifestParser)
7271

72+
for url in assetUrls {
7373
guard url.scheme == "http" || url.scheme == "https" else {
74-
NSLog("Unexpected asset link: \(link)")
74+
NSLog("Unexpected asset link: \(url)")
7575
continue
7676
}
7777

78-
_ = try await cacheAsset(from: url)
78+
try await cacheAsset(from: url)
7979
}
80-
NSLog("\(assetLinks.count) resources processed.")
80+
NSLog("\(assetUrls.count) resources processed.")
8181
}
8282

8383
/// Fetches one asset (JavaScript or stylesheet) and caches its content on the device.
8484
///
8585
/// - Parameters:
8686
/// - httpURL: The javascript or css URL.
8787
/// - webViewURL: The corresponding URL requested by web view, which should the "GBK cache prefix" (`gbk-cache-https://`)
88+
@discardableResult
8889
func cacheAsset(from httpURL: URL, webViewURL: URL? = nil) async throws -> (URLResponse, Data) {
8990
// The Web Inspector automatically requests ".js.map" files, we'll support it here for debugging purpose.
9091
let supportedResourceSuffixes = [".js", ".css", ".js.map"]
@@ -191,105 +192,106 @@ private extension String {
191192
}
192193
}
193194

194-
struct EditorAssetsMainifest: Codable {
195-
var scripts: String
196-
var styles: String
197-
var allowedBlockTypes: [String]
195+
public struct EditorAssetSchemeResolver {
196+
// Takes a URL string and applies the given scheme to it.
197+
//
198+
// If there is no scheme present, the `defaultScheme` will be applied to it. If no `defaultScheme` is
199+
// provided, `https` will be used.
200+
public static func resolveSchemeFor(_ link: String, defaultScheme: String?) -> String {
201+
if link.starts(with: "//") {
202+
return "\(defaultScheme ?? "https"):\(link)"
203+
}
204+
205+
return link
206+
}
207+
}
208+
209+
210+
// An object representing the JSON response we receive from the server
211+
//
212+
public struct EditorAssetManifest: Codable {
213+
public let scripts: String
214+
public let styles: String
215+
public let allowedBlockTypes: [String]
198216

199217
enum CodingKeys: String, CodingKey {
200218
case scripts
201219
case styles
202220
case allowedBlockTypes = "allowed_block_types"
203221
}
204222

205-
func parseAssetLinks(defaultScheme: String?) throws -> [String] {
206-
let html = """
207-
<html>
208-
<head>
209-
\(scripts)
210-
\(styles)
211-
</head>
212-
<body></body>
213-
</html>
214-
"""
215-
let document = try SwiftSoup.parse(html)
216-
217-
var assetLinks: [String] = []
218-
assetLinks += try document.select("script[src]").map {
219-
Self.resolveAssetLink(try $0.attr("src"), defaultScheme: defaultScheme)
220-
}
221-
assetLinks += try document.select(#"link[rel="stylesheet"][href]"#).map {
222-
Self.resolveAssetLink(try $0.attr("href"), defaultScheme: defaultScheme)
223-
}
224-
return assetLinks
223+
init(data: Data) throws {
224+
self = try JSONDecoder().decode(EditorAssetManifest.self, from: data)
225225
}
226226

227-
func renderForEditor(defaultScheme: String?) throws -> Data {
228-
var rendered = self
229-
rendered.scripts = try Self.renderForEditor(scripts: self.scripts, defaultScheme: defaultScheme)
230-
rendered.styles = try Self.renderForEditor(styles: self.styles, defaultScheme: defaultScheme)
231-
return try JSONEncoder().encode(rendered)
227+
init(scripts: String, styles: String, allowedBlockTypes: [String]) {
228+
self.scripts = scripts
229+
self.styles = styles
230+
self.allowedBlockTypes = allowedBlockTypes
232231
}
233232

234-
private static func renderForEditor(scripts: String, defaultScheme: String?) throws -> String {
235-
let html = """
236-
<html>
237-
<head>
238-
\(scripts)
239-
</head>
240-
<body></body>
241-
</html>
242-
"""
243-
let document = try SwiftSoup.parse(html)
244-
245-
for script in try document.select("script[src]") {
246-
if let src = try? script.attr("src") {
247-
let link = Self.resolveAssetLink(src, defaultScheme: defaultScheme)
248-
#if canImport(UIKit)
249-
let newLink = CachedAssetSchemeHandler.cachedURL(forWebLink: link) ?? link
250-
#else
251-
let newLink = link
252-
#endif
253-
try script.attr("src", newLink)
254-
}
255-
}
233+
func getScriptUrlStrings(using parser: EditorAssetManifestParser) throws -> [String] {
234+
try parser.extractScriptURLs(from: self.scripts)
235+
}
256236

257-
let head = document.head()!
258-
return try head.html()
237+
func getScriptUrls(using parser: EditorAssetManifestParser) throws -> [URL] {
238+
try getScriptUrlStrings(using: parser).compactMap(URL.init)
259239
}
260240

261-
private static func renderForEditor(styles: String, defaultScheme: String?) throws -> String {
262-
let html = """
263-
<html>
264-
<head>
265-
\(styles)
266-
</head>
267-
<body></body>
268-
</html>
269-
"""
270-
let document = try SwiftSoup.parse(html)
271-
272-
for stylesheet in try document.select(#"link[rel="stylesheet"][href]"#) {
273-
if let href = try? stylesheet.attr("href") {
274-
let link = Self.resolveAssetLink(href, defaultScheme: defaultScheme)
275-
#if canImport(UIKit)
276-
let newLink = CachedAssetSchemeHandler.cachedURL(forWebLink: link) ?? link
277-
#else
278-
let newLink = link
279-
#endif
280-
try stylesheet.attr("href", newLink)
281-
}
241+
func getStyleUrlStrings(using parser: EditorAssetManifestParser) throws -> [String] {
242+
try parser.extractStyleURLs(from: self.styles)
243+
}
244+
245+
func getStyleUrls(using parser: EditorAssetManifestParser) throws -> [URL] {
246+
try getStyleUrlStrings(using: parser).compactMap(URL.init)
247+
}
248+
249+
func getAllAssetUrls(applyingDefaultScheme scheme: String? = nil, using parser: EditorAssetManifestParser) throws -> [URL] {
250+
let scriptUrls = try self.getScriptUrls(using: parser)
251+
let styleUrls = try self.getStyleUrls(using: parser)
252+
253+
return scriptUrls + styleUrls
254+
}
255+
256+
func applyingUrlScheme(_ newScheme: String?, using manifestParser: EditorAssetManifestParser) throws -> Self {
257+
var mutableStyles = self.styles
258+
var mutableScripts = self.scripts
259+
260+
for rawLink in try getStyleUrlStrings(using: manifestParser) {
261+
let resolvedLink = EditorAssetSchemeResolver.resolveSchemeFor(rawLink, defaultScheme: newScheme)
262+
mutableStyles = mutableStyles.replacingOccurrences(of: rawLink, with: resolvedLink)
263+
}
264+
265+
for rawLink in try getScriptUrlStrings(using: manifestParser) {
266+
let resolvedLink = EditorAssetSchemeResolver.resolveSchemeFor(rawLink, defaultScheme: newScheme)
267+
mutableScripts = mutableScripts.replacingOccurrences(of: rawLink, with: resolvedLink)
282268
}
283269

284-
let head = document.head()!
285-
return try head.html()
270+
return EditorAssetManifest(
271+
scripts: mutableScripts,
272+
styles: mutableStyles,
273+
allowedBlockTypes: self.allowedBlockTypes
274+
)
286275
}
287276

288-
private static func resolveAssetLink(_ link: String, defaultScheme: String?) -> String {
289-
if link.starts(with: "//") {
290-
return "\(defaultScheme ?? "https"):\(link)"
277+
func resolvingCachedUrls(using manifestParser: EditorAssetManifestParser) throws -> Self {
278+
var mutableStyles = self.styles
279+
var mutableScripts = self.scripts
280+
281+
for url in try getStyleUrls(using: manifestParser) {
282+
let cachedLink = CachedAssetSchemeHandler.cachedURL(for: url)
283+
mutableStyles = mutableStyles.replacingOccurrences(of: url.absoluteString, with: cachedLink.absoluteString)
291284
}
292285

293-
return link
286+
for url in try getScriptUrls(using: manifestParser) {
287+
let cachedLink = CachedAssetSchemeHandler.cachedURL(for: url)
288+
mutableScripts = mutableScripts.replacingOccurrences(of: url.absoluteString, with: cachedLink.absoluteString)
289+
}
290+
291+
return EditorAssetManifest(
292+
scripts: mutableScripts,
293+
styles: mutableStyles,
294+
allowedBlockTypes: self.allowedBlockTypes
295+
)
294296
}
295297
}

0 commit comments

Comments
 (0)